You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
.gcloudignore does not import .gitignore. There is no #!include:.gitignore directive — line 6 /.gitignore merely excludes the ignore file itself. As a result, gcloud app deploy (run by pnpm deploy:web) uploads every file present in the working tree that is not explicitly listed in .gcloudignore, completely independent of git tracking status.
This trips GAE Standard's hard 10,000-file deploy cap: pnpm deploy:web does a full pnpm clean + pnpm build and then aborts at the gcloud app deploy step, after the expensive clean+build has already run.
Evidence (measured on a working tree with git status --porcelain reporting ZERO untracked files)
$ git status --porcelain --ignored=no | grep -c '^??'
0
$ gcloud meta list-files-for-upload . | wc -l
144737 # > 14x the 10,000-file GAE Standard cap
Top-level breakdown of the upload set:
132863 third_party # gitignored (.gitignore:19 `/third_party`), NOT in .gcloudignore
10534 .venv-pysimlin # gitignored (.gitignore:79 `.venv*`), NOT in .gcloudignore
1115 env # not gitignored, not in .gcloudignore
96 docs
31 .hypothesis
29 .claude
26 .agents
...
Two compounding root causes, both confirmed:
.gcloudignore is independent of .gitignore.third_party/ and .venv-pysimlin/ are gitignored (so git status is clean), yet both are uploaded because .gcloudignore has no matching rule and no #!include:.gitignore.
The /target/ rule is root-anchored..gcloudignore line 13 /target/ only excludes the repo-root target/. Nested cargo target dirs escape it — third_party/codex/codex-rs/target exists, and 108536 of the uploaded paths contain /target/.
An operator's real working tree is worse: the finding that surfaced this measured ~177k untracked files (third_party ≈ 143k, .venv-pysimlin ≈ 10.5k, src/pysimlin/.venv ≈ 7.3k, .venv ≈ 5k, env ≈ 1.1k, plus playwright-report/, test-results/, simlin/, src/pysimlin/{venv,build,dist}).
Why it matters
pnpm deploy:web fails at the gcloud step for any operator whose tree contains these common gitignored build/dep artifacts (third_party checkouts, Python venvs, cargo targets) — i.e. essentially any developed working tree. The failure happens only after a full clean+build, so it is slow to discover and costs a rebuild to retry.
The documented pre-deploy checklist gives NO protection.docs/dev/deploy.md lists "git status is clean" as a checklist item, but the upload set is independent of git state — a clean tree here still uploads 144,737 files. Operators following the checklist have a false sense of safety.
Two verifiers rated this medium as a code matter, blocker as an operational matter (it can hard-block a production deploy).
Relationship to existing tech-debt items (related but distinct)
tech-debt.md fix edit box when zoomed in #39 "Web deploy uploads the whole monorepo and GAE installs the full dep set" — that item is about dependency-set bloat: GAE's Node buildpack running pnpm install against the root workspace and pulling in firebase-tools/@rsbuild/jest/etc. on the instance. This item is different: it's about the upload set including gitignored/untracked files and hitting the 10,000-file hard cap before the buildpack ever runs. The clean fix is shared (the staging-directory / self-contained-bundle approach described in fix edit box when zoomed in #39 would also bound the upload set), but the failure mode, the measurement, and the interim mitigation are distinct.
tech-debt.md make highlight of item clearer #40 "Web deploy mutates tracked public/" — unrelated (cleanup of build output written into the tracked tree).
Constraint on the obvious fix
Naively adding #!include:.gitignore to .gcloudignore would break the deploy the other way: it would EXCLUDE the gitignored-but-load-bearing build output the deploy depends on uploading (src/*/lib, src/engine/core/libsimlin.wasm, public/static, etc. — see docs/dev/deploy.md "What gets uploaded"). So the fix needs either explicit re-includes of those paths or, preferably, the staging-directory/self-contained-bundle approach already discussed in #39.
Suggested interim fix (not implemented here)
Add explicit .gcloudignore exclusions for the high-count offenders: /third_party, /.venv*, /env, /simlin, /playwright-report, /test-results, /src/pysimlin/venv, /src/pysimlin/.venv, /src/pysimlin/build, /src/pysimlin/dist (and consider **/target/ to catch nested cargo targets, or per-path entries).
Add a real pre-deploy gate in docs/dev/deploy.md and/or scripts/deploy-web.sh:
gcloud meta list-files-for-upload .| wc -l # must be < 10000
This is the only check that actually reflects what gets uploaded; git status does not.
How it was discovered
Surfaced during a deploy-risk audit of the gcloud app deploy path. Confirmed in this repo by measuring gcloud meta list-files-for-upload . against a working tree with zero git-untracked files (still 144,737 files uploaded) and by confirming third_party/.venv-pysimlin are gitignored-but-uploaded and that the root-anchored /target/ rule lets third_party/codex/codex-rs/target through.
Summary
.gcloudignoredoes not import.gitignore. There is no#!include:.gitignoredirective — line 6/.gitignoremerely excludes the ignore file itself. As a result,gcloud app deploy(run bypnpm deploy:web) uploads every file present in the working tree that is not explicitly listed in.gcloudignore, completely independent of git tracking status.This trips GAE Standard's hard 10,000-file deploy cap:
pnpm deploy:webdoes a fullpnpm clean+pnpm buildand then aborts at thegcloud app deploystep, after the expensive clean+build has already run.Evidence (measured on a working tree with
git status --porcelainreporting ZERO untracked files)Top-level breakdown of the upload set:
Two compounding root causes, both confirmed:
.gcloudignoreis independent of.gitignore.third_party/and.venv-pysimlin/are gitignored (sogit statusis clean), yet both are uploaded because.gcloudignorehas no matching rule and no#!include:.gitignore./target/rule is root-anchored..gcloudignoreline 13/target/only excludes the repo-roottarget/. Nested cargo target dirs escape it —third_party/codex/codex-rs/targetexists, and108536of the uploaded paths contain/target/.An operator's real working tree is worse: the finding that surfaced this measured ~177k untracked files (third_party ≈ 143k, .venv-pysimlin ≈ 10.5k, src/pysimlin/.venv ≈ 7.3k, .venv ≈ 5k, env ≈ 1.1k, plus playwright-report/, test-results/, simlin/, src/pysimlin/{venv,build,dist}).
Why it matters
pnpm deploy:webfails at the gcloud step for any operator whose tree contains these common gitignored build/dep artifacts (third_party checkouts, Python venvs, cargo targets) — i.e. essentially any developed working tree. The failure happens only after a full clean+build, so it is slow to discover and costs a rebuild to retry.docs/dev/deploy.mdlists "git statusis clean" as a checklist item, but the upload set is independent of git state — a clean tree here still uploads 144,737 files. Operators following the checklist have a false sense of safety.Relationship to existing tech-debt items (related but distinct)
pnpm installagainst the root workspace and pulling infirebase-tools/@rsbuild/jest/etc. on the instance. This item is different: it's about the upload set including gitignored/untracked files and hitting the 10,000-file hard cap before the buildpack ever runs. The clean fix is shared (the staging-directory / self-contained-bundle approach described in fix edit box when zoomed in #39 would also bound the upload set), but the failure mode, the measurement, and the interim mitigation are distinct.Constraint on the obvious fix
Naively adding
#!include:.gitignoreto.gcloudignorewould break the deploy the other way: it would EXCLUDE the gitignored-but-load-bearing build output the deploy depends on uploading (src/*/lib,src/engine/core/libsimlin.wasm,public/static, etc. — see docs/dev/deploy.md "What gets uploaded"). So the fix needs either explicit re-includes of those paths or, preferably, the staging-directory/self-contained-bundle approach already discussed in #39.Suggested interim fix (not implemented here)
.gcloudignoreexclusions for the high-count offenders:/third_party,/.venv*,/env,/simlin,/playwright-report,/test-results,/src/pysimlin/venv,/src/pysimlin/.venv,/src/pysimlin/build,/src/pysimlin/dist(and consider**/target/to catch nested cargo targets, or per-path entries).docs/dev/deploy.mdand/orscripts/deploy-web.sh:git statusdoes not.How it was discovered
Surfaced during a deploy-risk audit of the
gcloud app deploypath. Confirmed in this repo by measuringgcloud meta list-files-for-upload .against a working tree with zero git-untracked files (still 144,737 files uploaded) and by confirmingthird_party/.venv-pysimlinare gitignored-but-uploaded and that the root-anchored/target/rule letsthird_party/codex/codex-rs/targetthrough.Components affected
.gcloudignorescripts/deploy-web.sh(pnpm deploy:web)docs/dev/deploy.md(pre-deploy checklist)