GCS Object Operations

Archived demo boundary

The original Storage project used throughout this note, bq-wh-nb, has been removed. Treat the captured object listings, generations, and notification outputs as archived operator reference, not as live validation.

This refresh intentionally avoids rerunning any object workflows. The note keeps the former outputs where they still illustrate the command behavior and only adds knowledge-backed corrections from current Cloud Storage documentation.

Why object-level behavior matters

Many operators understand buckets but still get surprised by object behavior. The common failures are not exotic:

  • A pipeline overwrites the right object name but the wrong generation.
  • A recursive delete targets the correct bucket but the wrong prefix.
  • A signed URL is valid, but the object metadata causes a browser or downstream client to treat it incorrectly.
  • A metadata patch races another patch and silently loses the intended value.
  • A restore succeeds, but the operator expected the same generation number instead of a new live generation.

Object operations are therefore about correctness as much as convenience.

Conceptual Model


stateDiagram-v2
    [*] --> Live : Upload or restore
    Live --> Live : Metadata patch<br/>metageneration +1
    Live --> NewGeneration : Overwrite in versioned bucket
    NewGeneration --> Live : Latest generation is current
    Live --> Held : temporary hold / event-based hold
    Held --> Live : hold released
    Live --> Retained : retain-until in future
    Retained --> Live : retention expires or unlocked retention cleared
    Live --> SoftDeleted : rm in soft-delete bucket
    SoftDeleted --> Live : restore

Inspect and Verify Objects

Before copying or deleting anything, inspect the real object estate. Listing, reading, describing, and hashing are the fastest ways to prevent accidental writes against the wrong prefix or the wrong file type.

PowerShell / Linux | gcloud storage ls, cat, objects describe, hash | inspect object state

This subsection uses the archived flat scratch bucket bq-wh-nb-codex-gcs-flat-20260413-3938.

List the archived object estate recursively

Before bulk copy, delete, lifecycle tuning, or prefix cleanup. It is typically triggered by you need to know what really exists under a bucket or prefix. Read-only listing. Show current prefixes, object sizes, and last-write timestamps.

List every object currently stored in the flat scratch bucket.

gcloud storage ls -l -r gs://bq-wh-nb-codex-gcs-flat-20260413-3938/
gs://bq-wh-nb-codex-gcs-flat-20260413-3938/archive/:
        58  2026-04-13T13:10:04Z  gs://bq-wh-nb-codex-gcs-flat-20260413-3938/archive/manifest.json
       123  2026-04-13T13:07:28Z  gs://bq-wh-nb-codex-gcs-flat-20260413-3938/archive/ohlcv.csv
 
gs://bq-wh-nb-codex-gcs-flat-20260413-3938/events/:
        58  2026-04-13T13:09:29Z  gs://bq-wh-nb-codex-gcs-flat-20260413-3938/events/manifest.json
 
gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/:
       123  2026-04-13T13:05:53Z  gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv
 
gs://bq-wh-nb-codex-gcs-flat-20260413-3938/sync/:
        46  2026-04-13T13:07:24Z  gs://bq-wh-nb-codex-gcs-flat-20260413-3938/sync/customers.csv
        26  2026-04-13T13:07:24Z  gs://bq-wh-nb-codex-gcs-flat-20260413-3938/sync/inventory.txt
TOTAL: 6 objects, 434 bytes (434.00B)

This single command answers three operator questions immediately: which prefixes exist, whether the object sizes look sane, and whether the last write time matches the expected pipeline run.

Read a small object without downloading it

When an object is small enough to inspect inline. It is typically triggered by you need to validate schema, manifest payload, or text content quickly. Read-only object read to stdout. Verify the object payload without writing a local copy first.

Print the CSV payload stored at landing/ohlcv.csv.

gcloud storage cat gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv
symbol,date,close,volume
ADYEN.AS,2026-04-11,1498.2,238741
ASML.AS,2026-04-11,812.4,421005
SAP.DE,2026-04-11,246.7,881223

Use cat only for small text-like payloads. For larger objects, download selectively or use client tooling that can read ranges.

Describe object metadata

After upload, metadata patching, overwrite, or restore. It is typically triggered by you need exact metadata fields, not just a listing row. Read-only metadata lookup. Confirm size, generation, metageneration, content type, and current metadata values.

Describe the archived landing/ohlcv.csv object snapshot used in this note.

gcloud storage objects describe gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv --format="yaml(name,size,content_type,generation,metageneration,storage_class,metadata)"
content_type: text/csv
generation: '1776085553882775'
metageneration: 9
name: landing/ohlcv.csv
size: 123
storage_class: STANDARD

The important distinction here is generation versus metageneration. The body is still generation 1776085553882775, but metadata changes have already advanced metageneration to 9.

Hash a local file before or after upload

Before upload, after download, or when debugging checksum mismatch. It is typically triggered by you need proof that a local file matches the object you expect. Local command only. No API mutation. Compute CRC32C and MD5 on the local file so you can compare them to the object metadata.

Calculate the local hashes for the source CSV file.

gcloud storage hash "C:\Users\aperi\AppData\Local\Temp\codex-gcs-notes-20260413\bronze\ohlcv.csv"
crc32c_hash: LmVpXQ==
digest_format: base64
md5_hash: /4BteChPGd+Cuqf6Q0MKJQ==
url: C:\Users\aperi\AppData\Local\Temp\codex-gcs-notes-20260413\bronze\ohlcv.csv

This is the cleanest way to confirm that the file you think you uploaded is the file you actually uploaded.

FlagSyntaxDescription
-lgcloud storage ls -l ...Shows object size and timestamp in listings.
-rgcloud storage ls -r ...Recurses into every matching prefix.
-agcloud storage ls -a ...Includes noncurrent generations when versioning is enabled.
--format--format="yaml(...)"Restricts output to the fields needed for the workflow.
--raw--rawReturns the underlying API payload instead of the normalized CLI view.
--soft-deletedgcloud storage objects describe ... --soft-deletedShows metadata for soft-deleted objects only.
--fetch-encrypted-object-hashes--fetch-encrypted-object-hashesRetries a describe request with a matching decryption key if needed.

Copy, Sync, Move, and Rewrite

The safest way to think about object movement is this:

  • cp creates another object.
  • rsync makes one path look like another path.
  • mv is copy plus delete, not an atomic rename.
  • Some updates, such as storage-class changes, are rewrites rather than lightweight metadata patches.

PowerShell / Linux | gcloud storage cp, rsync, mv, objects update | move object data safely

Copy an object to a colder prefix

When promoting or duplicating an object into another prefix without removing the source. It is typically triggered by A landing artifact needs to be archived or staged elsewhere. State-changing object copy. Create a second live object without mutating the source object.

Copy the latest landing CSV into the archive/ prefix.

gcloud storage cp gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv gs://bq-wh-nb-codex-gcs-flat-20260413-3938/archive/ohlcv.csv
Copying gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv to gs://bq-wh-nb-codex-gcs-flat-20260413-3938/archive/ohlcv.csv

cp is the safe default when the destination must exist before you even consider deleting the source.

Preview and run an incremental sync

Before publishing a directory of local files to GCS, especially when several files may already exist at the destination. It is typically triggered by A local export directory must be mirrored or incrementally copied into a bucket prefix. rsync can be read-only in dry-run mode or state-changing in normal mode. Show the delta before copying it, then apply only the needed transfers.

Preview the sync from the local sync-src directory into the bucket.

gcloud storage rsync --dry-run -r "C:\Users\aperi\AppData\Local\Temp\codex-gcs-notes-20260413\sync-src" gs://bq-wh-nb-codex-gcs-flat-20260413-3938/sync/
Would copy file://C:\Users\aperi\AppData\Local\Temp\codex-gcs-notes-20260413\sync-src\customers.csv to gs://bq-wh-nb-codex-gcs-flat-20260413-3938/sync/customers.csv
Would copy file://C:\Users\aperi\AppData\Local\Temp\codex-gcs-notes-20260413\sync-src\inventory.txt to gs://bq-wh-nb-codex-gcs-flat-20260413-3938/sync/inventory.txt

Run the sync for real.

gcloud storage rsync -r "C:\Users\aperi\AppData\Local\Temp\codex-gcs-notes-20260413\sync-src" gs://bq-wh-nb-codex-gcs-flat-20260413-3938/sync/
Copying file://C:\Users\aperi\AppData\Local\Temp\codex-gcs-notes-20260413\sync-src\customers.csv to gs://bq-wh-nb-codex-gcs-flat-20260413-3938/sync/customers.csv
Copying file://C:\Users\aperi\AppData\Local\Temp\codex-gcs-notes-20260413\sync-src\inventory.txt to gs://bq-wh-nb-codex-gcs-flat-20260413-3938/sync/inventory.txt
Average throughput: 1.5kiB/s

Windows rsync path warnings can be noisy

In this PowerShell session, gcloud storage rsync emitted warnings about characters that are invalid in Windows filenames while preparing its local walk state.

Trust the copy plan, not the temporary walker name

The important lines are the Would copy and Copying lines. Validate the source and destination URIs first, then treat the Windows temporary-name warning as implementation noise unless the sync itself fails.

Move an object after the destination is ready

Only after you are comfortable with copy-plus-delete semantics. It is typically triggered by an object must change prefix and the source should no longer remain live. State-changing copy followed by delete. Relocate the manifest from landing/ into archive/.

Move the manifest object into the archive prefix.

gcloud storage mv gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/manifest.json gs://bq-wh-nb-codex-gcs-flat-20260413-3938/archive/manifest.json
Copying gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/manifest.json to gs://bq-wh-nb-codex-gcs-flat-20260413-3938/archive/manifest.json
Removing gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/manifest.json...

mv is convenient, but the output tells you the truth: it is not a metadata rename.

Change storage class on an object

When one object should move to a colder class without waiting for bucket lifecycle evaluation. It is typically triggered by an archive artifact is ready for cold storage immediately. State-changing object rewrite. Show that a storage-class change creates a rewritten object generation.

Rewrite archive/manifest.json into NEARLINE.

gcloud storage objects update gs://bq-wh-nb-codex-gcs-flat-20260413-3938/archive/manifest.json --storage-class=NEARLINE
Rewriting gs://bq-wh-nb-codex-gcs-flat-20260413-3938/archive/manifest.json...
gcloud storage objects describe gs://bq-wh-nb-codex-gcs-flat-20260413-3938/archive/manifest.json --raw
generation: '1776085804390461'
name: archive/manifest.json
size: '58'
storageClass: NEARLINE

The verb matters here. The CLI said Rewriting, not Patching, because class changes are object rewrites, not pure metadata flips.

FlagSyntaxDescription
-rgcloud storage cp -r ... or rsync -r ...Recurse into subdirectories or prefixes.
-ngcloud storage cp -n ...No-clobber copy; skip existing destination objects.
--dry-rungcloud storage rsync --dry-run ...Preview the sync plan without applying it.
-dgcloud storage rsync -d ...Delete destination objects that do not exist in the source. Use with extreme care.
--include--include="*.parquet"Restrict copy or sync to matching files.
--exclude--exclude="*.tmp"Exclude matching files from copy or sync.
--content-type--content-type=text/csvOverrides auto-detected content type during upload.
--storage-class--storage-class=NEARLINERewrites the object into a different storage class.
--continue-on-error--continue-on-errorContinue object operations after individual failures.

Generations, Metadata, and Preconditions

Safe automation depends on knowing whether you are updating the object body or only the metadata, and on refusing to write when another actor has changed the object since your last read.

PowerShell / Linux | gcloud storage ls and objects update | prevent blind overwrite

Show every generation of a versioned object

After overwrite or when investigating versioning behavior. It is typically triggered by one logical object name has been written more than once. Read-only listing against a versioned bucket. Prove that versioning is preserving older generations instead of mutating one in place.

List every generation of the landing CSV.

gcloud storage ls -a gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv
gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv#1776085535415169
gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv#1776085553882775

The same object name now has two generations. The higher generation is current; the lower one remains available for recovery or audit.

Patch metadata with explicit preconditions

When a workflow must update metadata safely after first reading object state. It is typically triggered by you need to add metadata or fix content type without risking a lost update. State-changing metadata patch. Preconditions turn it into an optimistic-concurrency pattern. Update content type and custom metadata only if the expected generation and metageneration still match.

Patch the object safely using both generation and metageneration preconditions.

gcloud storage objects update gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv \
  --content-type=text/csv \
  "--custom-metadata=zone=landing,format=csv" \
  --if-generation-match=1776085553882775 \
  --if-metageneration-match=2
Patching gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv...

After the patch, the object still has generation 1776085553882775, but its metageneration advanced and the metadata now contains zone and format.

Two precondition patterns matter most

Current Cloud Storage guidance highlights two operator-grade patterns:

  • Use --if-generation-match together with --if-metageneration-match when you are patching metadata that depends on a previously read object state.
  • Use --if-generation-match=0 when the goal is create-only semantics, meaning the write should succeed only if no live object with that name currently exists.

Show the failure path for a stale precondition

During automation testing or when explaining why optimistic concurrency is safer than blind patching. It is typically triggered by another metadata update has already advanced metageneration. State-changing command expected to fail safely. Demonstrate that stale preconditions fail with 412 instead of silently overwriting current metadata.

Retry a metadata patch with a stale metageneration.

gcloud storage objects update gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv --update-custom-metadata=owner=pipeline --if-metageneration-match=1
Patching gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv...
ERROR: HTTPError 412: At least one of the pre-conditions you specified did not hold.

This is the outcome you want. A stale writer failed fast instead of trampling the newer object state.

FlagSyntaxDescription
--content-type--content-type=text/csvSets or corrects the stored MIME type.
--custom-metadata"--custom-metadata=zone=landing,format=csv"Replaces the full custom-metadata set.
--update-custom-metadata--update-custom-metadata=owner=pipelineAdds or updates individual metadata keys without clearing the rest.
--remove-custom-metadata--remove-custom-metadata=ownerDeletes one or more custom-metadata keys.
--if-generation-match--if-generation-match=1776085553882775Runs only if the object body generation is exactly the expected one. 0 is the create-only pattern when no live object may already exist.
--if-metageneration-match--if-metageneration-match=2Runs only if the metadata version is exactly the expected one.

Holds, Retention, and Object Restore

These controls decide whether an object may be changed or deleted right now:

  • Temporary hold is manual and indefinite until released.
  • Event-based hold stays active until a workflow explicitly releases it.
  • Per-object retention uses time instead of a manual hold.
  • Soft delete makes delete reversible for a limited period.

PowerShell / Linux | gcloud storage objects update, rm, restore | block and recover object mutation

Apply and release a temporary hold

During manual review, incident containment, or handoff approval. It is typically triggered by an object must not be deleted or overwritten until a human clears it. State-changing metadata patch. Show manual mutation blocking without changing the object body.

Enable a temporary hold on the landing CSV.

gcloud storage objects update gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv --temporary-hold
Patching gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv...
gcloud storage objects describe gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv --raw
generation: '1776085553882775'
temporaryHold: true
metageneration: '4'

Release the temporary hold again.

gcloud storage objects update gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv --no-temporary-hold
Patching gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv...

Apply and release an event-based hold

When a workflow must validate data before allowing later mutation or delete. It is typically triggered by newly landed data should stay frozen until a release step completes. State-changing metadata patch. Show the object-level hold that mirrors the bucket-level default event-based hold pattern.

Enable the event-based hold on the same object.

gcloud storage objects update gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv --event-based-hold
Patching gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv...
gcloud storage objects describe gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv --raw
eventBasedHold: true
generation: '1776085553882775'
metageneration: '6'

Release the event-based hold.

gcloud storage objects update gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv --no-event-based-hold
Patching gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv...

Set and clear per-object retention

When one object in a mixed-use bucket needs its own minimum retention horizon. It is typically triggered by A delivery file or regulatory artifact must not be deleted before a specific timestamp. State-changing object-retention update. Requires a bucket created with per-object retention enabled. Apply an object-specific retain-until timestamp and then clear it while still unlocked.

Set an unlocked object retention window 15 minutes into the future.

gcloud storage objects update gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv \
  --retention-mode=Unlocked \
  --retain-until=2026-04-13T13:21:58Z
Patching gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv...
gcloud storage objects describe gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv --raw
retention:
  mode: Unlocked
  retainUntilTime: '2026-04-13T13:21:58+00:00'
retentionExpirationTime: '2026-04-13T13:21:58+00:00'

Clear the unlocked retention again.

gcloud storage objects update gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv --clear-retention --override-unlocked-retention
Patching gs://bq-wh-nb-codex-gcs-flat-20260413-3938/landing/ohlcv.csv...

Delete, inspect, and restore a soft-deleted object

During restore drills or after an accidental delete. It is typically triggered by an object was removed from a soft-delete-enabled bucket. Destructive delete followed by read-only inspection of soft-deleted metadata and then a state-changing restore. Show exactly what soft-deleted object metadata looks like and how restore creates a new live generation.

Upload a disposable object into the HNS bucket, delete it, inspect the soft-deleted version, and restore it.

gcloud storage cp "C:\Users\aperi\AppData\Local\Temp\codex-gcs-notes-20260413\sync-src\customers.csv" gs://bq-wh-nb-codex-gcs-hns-20260413-2288/raw/bronze/2026/04/13/customers.csv
Copying file://C:\Users\aperi\AppData\Local\Temp\codex-gcs-notes-20260413\sync-src\customers.csv to gs://bq-wh-nb-codex-gcs-hns-20260413-2288/raw/bronze/2026/04/13/customers.csv
gcloud storage rm gs://bq-wh-nb-codex-gcs-hns-20260413-2288/raw/bronze/2026/04/13/customers.csv
Removing gs://bq-wh-nb-codex-gcs-hns-20260413-2288/raw/bronze/2026/04/13/customers.csv...
gcloud storage objects describe gs://bq-wh-nb-codex-gcs-hns-20260413-2288/raw/bronze/2026/04/13/customers.csv#1776085711778976 --soft-deleted --raw
generation: '1776085711778976'
hardDeleteTime: '2026-04-20T13:08:36.187000+00:00'
restoreToken: 603ae31f-cf15-4709-a70b-cc7363f3bd83
softDeleteTime: '2026-04-13T13:08:36.187000+00:00'
name: raw/bronze/2026/04/13/customers.csv
gcloud storage restore gs://bq-wh-nb-codex-gcs-hns-20260413-2288/raw/bronze/2026/04/13/customers.csv#1776085711778976
Restoring gs://bq-wh-nb-codex-gcs-hns-20260413-2288/raw/bronze/2026/04/13/customers.csv#1776085711778976...
gcloud storage objects describe gs://bq-wh-nb-codex-gcs-hns-20260413-2288/raw/bronze/2026/04/13/customers.csv --raw
generation: '1776085720624146'
name: raw/bronze/2026/04/13/customers.csv
size: '46'
storageClass: STANDARD

The restored object did not come back with the same generation number. Restore made a new live generation from the deleted payload, which is exactly how you should expect recovery to behave.

Restore token and storage-class nuances

Current Cloud Storage documentation adds two details that matter in real incidents:

  • In hierarchical-namespace buckets, duplicate soft-deleted objects can require a restoreToken to disambiguate which deleted instance to recover.
  • A restored live object comes back in STANDARD storage, regardless of the storage class of the soft-deleted source object.
FlagSyntaxDescription
--temporary-hold--temporary-holdEnables a manual hold on the object.
--no-temporary-hold--no-temporary-holdReleases the manual hold.
--event-based-hold--event-based-holdEnables the event-based hold on the object.
--no-event-based-hold--no-event-based-holdReleases the event-based hold.
--retention-mode--retention-mode=UnlockedSets the object retention mode.
--retain-until--retain-until=2026-04-13T13:21:58ZSets the retention expiry for the object.
--clear-retention--clear-retentionRemoves object-level retention settings.
--override-unlocked-retention--override-unlocked-retentionRequired when shortening or clearing unlocked retention.
--soft-deletedgcloud storage objects describe ... --soft-deletedRestricts describe output to soft-deleted objects.
--all-versionsgcloud storage restore ... --all-versionsRestores every soft-deleted version in order.

Controlled Sharing and Event-Driven Automation

Some object workflows are not about storage at rest at all. They are about how objects leave the platform or trigger downstream work.

PowerShell / Linux | gcloud storage sign-url, buckets notifications, pubsub | publish and react to object events

Generate a signed URL for controlled delivery

When an external consumer needs temporary access to one object but should not receive project IAM. It is typically triggered by manual download handoff, partner delivery, dashboard export, or short-lived distribution path. Read-only signing operation that requires signing credentials rather than object mutation rights. Produce a time-limited URL for archive/ohlcv.csv.

Generate a ten-minute signed URL using the project service-account key.

gcloud storage sign-url gs://bq-wh-nb-codex-gcs-flat-20260413-3938/archive/ohlcv.csv --private-key-file="C:\Users\aperi\My Drive\VAULT\gcp-bq-key.json" --duration=10m
expiration: '2026-04-13 13:18:48'
http_verb: GET
resource: gs://bq-wh-nb-codex-gcs-flat-20260413-3938/archive/ohlcv.csv
signed_url: https://bq-wh-nb-codex-gcs-flat-20260413-3938.storage.googleapis.com/archive/ohlcv.csv?x-goog-signature=...

The important output fields are expiration, http_verb, and signed_url. The URL is valid only until the expiration time and only for the signed verb.

Signed URL scope and lifetime

Current Cloud Storage documentation keeps the core V4 constraint unchanged: signed URLs are for bounded access to a specific request shape, and the maximum lifetime is seven days.

They are also most useful for the initial request boundary. For resumable uploads, once the session URI is created, subsequent upload requests authenticate with that session URI rather than with another signed URL.

Create a bucket notification and pull the resulting Pub/Sub message

When downstream processing should react to new objects rather than polling for them. It is typically triggered by object-arrival workflows, ingestion fan-out, or audit/event pipelines. State-changing configuration across Cloud Storage and Pub/Sub. Requires pubsub.googleapis.com, a topic, a subscription, and permission for the Cloud Storage service agent to publish. Show an end-to-end finalize event for objects written under the events/ prefix.

Enable the Pub/Sub API in the active project.

gcloud services enable pubsub.googleapis.com
Operation "operations/acf.p2-348557092514-da55c27c-cd10-4fa7-ad6a-736e25f2a6f8" finished successfully.

Create the Pub/Sub topic used by the notification path.

gcloud pubsub topics create gcs-events-demo
Created topic [projects/bq-wh-nb/topics/gcs-events-demo].

Create the subscription that will consume the bucket events.

gcloud pubsub subscriptions create gcs-events-demo-sub --topic=gcs-events-demo
Created subscription [projects/bq-wh-nb/subscriptions/gcs-events-demo-sub].

Create an object-finalize notification for the events/ prefix and list it back.

gcloud storage buckets notifications create gs://bq-wh-nb-codex-gcs-flat-20260413-3938 --topic=gcs-events-demo --event-types=OBJECT_FINALIZE --object-prefix=events/
Bucket URL: gs://bq-wh-nb-codex-gcs-flat-20260413-3938/
Notification Configuration:
  id: '6'
  event_types:
  - OBJECT_FINALIZE
  object_name_prefix: events/
  payload_format: JSON_API_V1
  topic: //pubsub.googleapis.com/projects/bq-wh-nb/topics/gcs-events-demo
gcloud storage buckets notifications list gs://bq-wh-nb-codex-gcs-flat-20260413-3938
Bucket URL: gs://bq-wh-nb-codex-gcs-flat-20260413-3938/
Notification Configuration:
  id: '6'
  event_types:
  - OBJECT_FINALIZE
  object_name_prefix: events/
  payload_format: JSON_API_V1
  topic: //pubsub.googleapis.com/projects/bq-wh-nb/topics/gcs-events-demo

Upload a matching object into the events/ prefix.

gcloud storage cp "C:\Users\aperi\AppData\Local\Temp\codex-gcs-notes-20260413\bronze\manifest.json" gs://bq-wh-nb-codex-gcs-flat-20260413-3938/events/manifest.json
Copying file://C:\Users\aperi\AppData\Local\Temp\codex-gcs-notes-20260413\bronze\manifest.json to gs://bq-wh-nb-codex-gcs-flat-20260413-3938/events/manifest.json

Pull one Pub/Sub message from the subscription.

gcloud pubsub subscriptions pull gcs-events-demo-sub --auto-ack --limit=1
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------+------------+
| DATA                                                                                                                                                                        | MESSAGE_ID        | ACK_STATUS |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------+------------+
| {                                                                                                                                                                           | 18217658696904028 | SUCCESS    |
|   "id": "bq-wh-nb-codex-gcs-flat-20260413-3938/events/manifest.json/1776085769906203",                                                                                     |                   |            |
|   "name": "events/manifest.json",                                                                                                                                           |                   |            |
|   "bucket": "bq-wh-nb-codex-gcs-flat-20260413-3938",                                                                                                                        |                   |            |
|   "generation": "1776085769906203",                                                                                                                                         |                   |            |
|   "contentType": "application/json",                                                                                                                                        |                   |            |
|   "size": "58"                                                                                                                                                              |                   |            |
| }                                                                                                                                                                           |                   |            |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------+------------+

The pulled message confirms that the OBJECT_FINALIZE event carried the object name, bucket, generation, content type, and size into Pub/Sub. That is the foundation for event-driven ingestion.

Notification semantics worth remembering

Current Cloud Storage documentation adds three operational details that are easy to miss:

  • A bucket can have up to 100 total notification configurations and up to 10 notification configurations for the same event type.
  • Creating or deleting a notification configuration increments the bucket metageneration.
  • Replacing an existing object generates OBJECT_FINALIZE for the new generation and either OBJECT_ARCHIVE or OBJECT_DELETE for the prior object state, with overwroteGeneration carried on the finalize message.
FlagSyntaxDescription
--private-key-file--private-key-file=key.jsonUses a local private key to sign the URL.
--duration--duration=10mSets how long the signed URL remains valid.
--http-verb--http-verb=PUTSigns a verb other than GET, such as upload.
--headers--headers=Content-Type=text/csvBinds required request headers into the signature.
--topic--topic=gcs-events-demoSets the destination Pub/Sub topic for a notification.
--event-types--event-types=OBJECT_FINALIZERestricts the notification to specific event types.
--object-prefix--object-prefix=events/Restricts the notification to matching object names.
--payload-format--payload-format=jsonControls whether the message contains JSON payload or attributes only.
--skip-topic-setup--skip-topic-setupSkips topic creation and publish-permission setup.
--auto-ack--auto-ackAcknowledges pulled Pub/Sub messages automatically.

Data-Engineering Object Layout Patterns

The object name is part of the data model. A good prefix layout makes lifecycle policy, restore, replay, and downstream consumption much easier.

Layout patternExampleWhy it works
Landing date prefixraw/source_a/2026/04/13/file.csvEasy replay windows and partition-style isolation.
Medallion zoneslanding/, archive/, sync/, events/Separates ingest, retained delivery, sync state, and event-trigger paths.
Export stagingexport/job_id/part-000.parquetKeeps one export run isolated from the next.
Replay bucket or prefixreplay/2026-04-01/Makes backfills and audit reruns explicit instead of hidden in the hot path.
Small-file consolidationFewer larger Parquet or compressed CSV objectsReduces listing overhead, object-count sprawl, and downstream job startup cost.

Native composition is part of the compaction toolbox

Cloud Storage supports native object composition for 1 to 32 source objects into one composite object. That makes gcloud storage objects compose a legitimate building block for bounded small-file consolidation inside one bucket when you do not yet need a full transfer or processing service.

Not executed live

Storage Transfer Service is the right tool when the transfer is large, scheduled, cross-cloud, or operationally important enough that retries, scheduling, and auditability should live in a managed service instead of in ad-hoc cp or rsync loops. A transfer job was not created here because the teaching goal was object mechanics inside the current project rather than long-running migration infrastructure.

Troubleshooting and Runbooks

SymptomLikely causeWhat to inspect firstSafe next step
412 precondition failureObject generation or metageneration changed since last readCurrent generation and metageneration from objects describeRe-read object state and retry with fresh preconditions.
Checksum mismatch after transferWrong source file, truncated upload, or downstream rewriteLocal gcloud storage hash versus object hashesRe-upload from the known-good file and compare again.
Object not found even though the prefix existsWrong full object name or folder/prefix confusionExact object name and prefix layoutList the prefix first, then use the full returned object path.
Delete succeeded but object must come backSoft-delete window is activesoftDeleteTime, hardDeleteTime, generationRestore immediately and record the new live generation.
Signed URL failsExpired signature, wrong verb, wrong headers, or invalid signer keyexpiration, http_verb, and signed headersRegenerate the URL with the exact verb and headers the caller will use.
Recursive sync or delete feels riskyWrong source or destination pathDry-run output and bucket listingRun the dry-run first and treat it as mandatory review.
Notification never arrivesPub/Sub API disabled, topic missing, wrong prefix filter, or service agent cannot publishTopic, notification config, object prefix, Pub/Sub pull resultRe-list notification config and confirm the uploaded object actually matches the configured prefix.

Quick Reference

OperationSafest command surfaceRisk to watch
Inspect object estategcloud storage ls -l -rLarge buckets can produce long listings; scope the prefix when possible.
Compare versionsgcloud storage ls -aWithout versioning, only one live generation exists.
Safe metadata patchgcloud storage objects update --if-generation-match --if-metageneration-matchBlind patching can lose concurrent updates.
Manual freeze--temporary-holdForgetting to clear it blocks lifecycle and delete.
Workflow freeze--event-based-holdPipelines must include an explicit release step.
Per-object retention--retention-mode --retain-untilClearing or shortening requires --override-unlocked-retention.
Accidental delete recoverygcloud storage restoreRestore creates a new live generation; it does not resurrect the old one in place.
External deliverygcloud storage sign-urlExpiry, verb, and header mismatches are the common failures.

GCS Object Operations References