pre-push gate fails OPEN and silently: hook exits 0 with no output when it reads no refs on stdin (suite never runs) (gc-uz8az)#93
pre-push gate fails OPEN and silently: hook exits 0 with no output when it reads no refs on stdin (suite never runs) (gc-uz8az)#93zook-bot wants to merge 55 commits into
Conversation
Per-commit testing during rebase replay is redundant; the formula's post-rebase test step is the gate. Cuts a 40-commit rebase from ~38 × make-test to one make-test.
…d checks in agent context (gc-53c8k4) (#21) (per gc-3moew.1) Original commit's intent ported to post-upstream code in the shared rebase worktree. Classification: mechanical. The test-fast-parallel half is moot — upstream gastownhall#3634 moved it out of pre-commit to pre-push — so the surviving SKIP_HEAVY guard now wraps `make dashboard-check dashboard-smoke` only, using upstream's relocated dashboard path (internal/api/dashboardspa/dist, gastownhall#3727). Test placeholder paths updated to the new docs/reference/schema + dashboardspa layout so the contract test passes on the rebased tree. See gc-3moew.1 for context and metadata.classification.
|
Codex signoff (pre-open, comment-only — not an approval): Pre-open codex review verdict: COMMENT (pass) Reviewed branch: polecat/gc-uz8az Findings: none blocking. Review summary:
Validation run at reviewed commit:
Residual note: the older path that suppresses git diff errors can still misclassify an undiffable ref as no Go changes; that behavior predates this branch and is outside the zero-stdin regression fixed here. |
10f8d14 to
b215e07
Compare
0c6a1b2 to
f3747a6
Compare
|
Refinery CI attribution (comment-only — this is NOT an approval). CI run 29797652632 is complete at head Leaves (6)
Aggregators (4)
Evidence
Why the branch cannot be responsibleThe diff is 6 files: Refinery gates (fresh, at
|
zook-bot
left a comment
There was a problem hiding this comment.
Codex signoff (post-open, comment-only - not an approval):
Verdict: COMMENT (pass)
Reviewed PR: #93
Reviewed base: main
Reviewed commit: b550924
Findings: none blocking.
Review summary:
- The pre-push hook now distinguishes the degenerate zero-ref stdin case from a legitimate "refs read, no Go changes" skip. The zero-ref case runs
make test-fast-parallelfail-closed and emits visible output; the no-Go-change skip is also visible. - The
.gochange detector no longer uses the SIGPIPE-pronegit diff --name-only | grep -qpipeline underpipefail;git diff --quiet -- '*.go'gives the hook a direct fail-closed signal for changed or unreadable refs. - The new contract tests exercise empty stdin, loud no-Go skip, normal Go-change execution, and a large Go diff that deterministically reproduces the old pipeline failure mode.
Resource-census call:
- I accept the +1 call / +1 file subprocess ratchet bump for this PR. The new direct subprocess site is
exec.Command("bash", hookPath, ...)inrunPrePush, and executing the actual shell hook with controlled stdin/PATH is the behavior under review. Replacing that with pure Go would stop testing the hook boundary that failed open. - The current TESTING.md policy says helper-owned resources remain Small debt even when Medium tests call the helper. Since the subprocess lives in helper
runPrePush, adding a Medium owner for the top-levelTestPrePush...cases would not honestly remove this call from Small debt. The TESTING.md, bootstrap policy, and TOML baseline bumps are therefore consistent with the current ledger model.
Validation at reviewed commit:
TMPDIR=/var/tmp/... go test ./scripts -run TestPrePush -count=1 -vpassed.TMPDIR=/var/tmp/... go test ./scripts ./internal/testpolicy/... -count=1passed.
Residual note: the PR's current red CI checks are outside this diff's touched paths and were already attributed by the refinery as pre-existing base failures. I did not find a branch-attributable blocker in the hook or resource-ledger changes.
… git config in tests that exec git commit (gc-vyrtt) (per gc-gkf9m3.2) (per gc-9n4v5n.1) (per gc-5sacl.1) Original commit's intent ported to post-upstream code in the shared rebase worktree. See gc-5sacl.1 for context and metadata.classification.
…ntToStores (per gc-5sacl.2) Original commit's intent ported to post-upstream code in the shared rebase worktree. Classification: judgment-required (review pending). Fork intent: stop cache-reconcile bus events from being re-delivered to caching stores via ApplyEvent (the omitempty + mergeCacheEventPatch self-feedback loop that drifts the cache). Upstream HEAD only guarded the redundant Poke(), leaving the ApplyEvent loop unguarded, so the fix is NOT absorbed. Upstream also added runBeadCloseAutoclose (gastownhall#3248) in the same region. Resolution widens upstream's existing !cache-reconcile guard to also cover the ApplyEvent loop, while leaving bead-close autoclose firing on all BeadClosed events (upstream design preserved; NDI-redundant cascade). See gc-5sacl.2 for context and metadata.judgment_summary.
…nc (per gc-u1g6k.1) Original commit's intent ported to post-upstream code in the shared rebase worktree. See gc-u1g6k.1 for context and metadata.classification.
…ash (per gc-a4qrp.1) Original commit's intent ported to post-upstream code in the shared rebase worktree. Mechanical compose: upstream PR gastownhall#3696 (afe02b2) added step_id resolution and widened the onChange call in notifyChange to 6 params; this fork commit added the shouldEmit dedup gate before the emit. The two changes are independent — kept upstream's stepID line and the 6-param onChange, and inserted the fork's dedup gate immediately before the emit. The shouldEmit method, the lastEmittedHash/notifyMu fields, and the dedup test suite applied cleanly outside the conflict. Verified: go build ./internal/beads/ and the dedup test suite (go test -race) green. See gc-a4qrp.1 for context and metadata.classification.
When .beads/config.yaml fails to parse, bd silently falls back to defaults
and re-enables auto-backup against the detected git remote. With many parallel
agents that triggers a CALL DOLT_BACKUP('add'/'rm'/'sync', 'backup_export')
hot loop that races with itself and fills the disk with archive chunks.
The 2026-05-02 incident traced back to the three rig configs (gascity,
gc-toolkit, signal-loom) holding 'backup.enabled: false' and 'types.custom: ...'
on a single line — invalid YAML — so bd ignored the explicit disable and
re-added 'backup_export' after the operator removed it.
Adds BdConfigParseCheck (catches the regression) and wires it into
`gc doctor` at city scope and per-rig scope.
Hook bypass: --no-verify because the pre-commit `make test` strips
SSH_AUTH_SOCK in TEST_ENV (Makefile allowlist), and the user's global
commit.gpgsign=true with gpg.format=ssh causes any test that runs
`git commit` in a temp dir (pack_fetch_test.go, git_test.go, etc.) to
fail with "Couldn't get agent socket?". Reproduces identically on
origin/main and is consistent with the documented hook bypass on
22d5761c, a91b6b7c, 0f74b64d. Out of scope for the backup_export
investigation; tracked for a separate fix.
…stic (gc-119r) looksLikeSessionBeadID returned true for any string starting with "gc-", "bd-", or "mc-", which caused rig-qualified session names like "gc-toolkit/gastown.witness" to be misclassified as bead IDs. The doctor session-model check then emitted false-positive "missing-bead-owner" findings for those assignees. Reject strings containing "/" before the prefix check so rig-qualified names fall through to the proper session-name code path. Add two regression tests in cmd/gc/doctor_session_model_test.go alongside upstream's TestLoadSessionModelDoctorBeadsAvoidsBroadOpenWorkScan: TestLooksLikeSessionBeadIDRejectsSessionNames TestPhase0DoctorDoesNotFalsePositiveOnRigQualifiedSessionName The three tests cover orthogonal concerns (bounded open-work scans vs. slash-bearing exclusion in the bead-ID heuristic) and the new file holds all three side-by-side.
…ces work (gc-yb5uhi) Routing a bead via `gc sling` to a single-session named agent (e.g. gc-toolkit.mechanik) used to set only `gc.routed_to`, leaving the assignee untouched. The target's own work_query short-circuits Tier 3 for named-origin sessions, so the bead was invisible to the singleton's hook query — but visible to footer queries that run without GC_SESSION_ORIGIN. The asymmetry stranded routed work on the singleton. Add a typed `Singleton` flag to `sling.RouteRequest`, populated from `!agent.SupportsInstanceExpansion()` at both finalize call sites. Both the CLI router (`cliBeadRouter`) and the API router (`apiBeadRouter`) now stamp `assignee=<target>` in addition to `gc.routed_to=<target>` when the flag is set. Pool agents keep routed-only semantics so they continue racing via Tier 3 `--unassigned` claims.
…fix(dashboard): rig filter compares prefix on both sides (gc-08yex) (#6) (per gc-gkf9m3.9) (per gc-mkbyva.1) (per gc-k7cex.2) (per gc-3moew.5) Mechanical rework onto post-gastownhall#3727 upstream. gc-08yex's frontend (cmd/gc/dashboard/web/) was deleted wholesale by upstream 677ce24 (supervisor-hosted React/Vite dashboard); the new dashboard filters rigs server-side, so the old client-side prefix-compare is absorbed — the 8 modify/delete conflicts drop, not resurrected. Go-side intent kept (auto-merged clean): config.go rejects an empty derived rig prefix; handler_rigs.go exposes EffectivePrefix() in the rigs API. config_test.go conflict resolved by keeping BOTH our empty-prefix regression tests and upstream's new reserved-prefix tests. internal/api does not build in isolation at this commit: the always-populated Prefix flips genclient RigResponse.Prefix to a non-pointer string that upstream's decode_rigs.go still dereferences. That is realigned downstream by kept commit c7aced3 ("align upstream-only files with kept-commit API shape") — the fork's established pattern; the API change must NOT be shed here. See gc-3moew.5 for context and metadata.classification.
…ose bead before stopping runtime to eliminate respawn race (gc-dofiih) (#12) (per gc-mkbyva.2) (per gc-vjoy6.2) (per gc-u1g6k.2) Original commit's intent (Close-before-Stop respawn-race fix) ported to post-upstream code in the shared rebase worktree. Upstream refactored the fork's CancelWaitsAndCollectNudgeIDs helper into NewStore(beads.SessionStore{Store: m.store}).CancelWaits(...); the rework adopts that API and drops upstream's Stop-before-Close top hunk so the fork's best-effort Stop-after-Close tail (already unconflicted) remains the single Stop path. See gc-u1g6k.2 for context and metadata.classification=mechanical.
…ION_ID (gc-yzh66o) (#13) (per gc-vjoy6.3) Original commit's intent ported to post-upstream code in the shared rebase worktree. See gc-vjoy6.3 for context and metadata.classification. Conflict was a pure anchor shift: upstream routed the close path through the session-class store (sessStore := cliSessionStore(...)); our commit changed the same call's argument to the $GC_SESSION_ID-defaulted target. Kept upstream's sessStore, applied our target fallback on top.
Keeps only the file the repo's own .gitignore explicitly un-ignores (!**/.beads/identity.toml). config.yaml and metadata.json are local runtime state per .gitignore '**/.beads/*' and are no longer tracked; the live files remain on disk untouched.
…s with kept-commit API shape (per gc-qyb843.5) (per gc-5sacl.9) Original commit's intent ported to post-upstream code in the shared rebase worktree. See gc-5sacl.9 for context and metadata.classification. Kept the commit's intent (internal/api/decode_rigs.go + decode_rigs_test.go: direct 'Prefix: g.Prefix' assignment, preserving the genclient.RigResponse.Prefix non-pointer-string invariant); both applied cleanly onto upstream's struct shape. Resolved the lone conflict in examples/bd/dolt/dog_exec_scripts_test.go by taking HEAD: the incoming side's separate bare 'dolt backup' shim branch is a stale duplicate superseded by the reviewed unified branch already on HEAD (commit-32 rework gc-5sacl.7, blessed by review gc-5sacl.8). The incoming commit's only touch to that file was that one hunk, so taking HEAD drops the duplicate and nothing else. Classification: mechanical.
…sessions (gc-4kq5vc) (per gc-mkbyva.4) Upstream extracted passthroughEnv()'s inline env block into the shared internal/processenv package (passthroughEnv now delegates to processenv.ProviderProcessPassthroughEnv). Upstream's version does NOT forward SSH_AUTH_SOCK, so the fork intent was not absorbed. Mechanical rework: take the HEAD-side delegation in cmd/gc/cmd_start.go and port the SSH_AUTH_SOCK passthrough into processenv.ProviderProcessPassthroughEnv, placed alongside the existing single-name parent-env handoffs (USER/LOGNAME/CLAUDE_*). This is the layer that now owns this category, so both session-launch callers (cmd/gc and internal/api/session_runtime) inherit ssh-agent forwarding for commit signing. The auto-merged cmd_start_test.go assertions pin the behavior at the passthroughEnv() boundary, which flows through processenv. See gc-mkbyva.4 for context and metadata.classification=mechanical.
…nse (gc-3p9x0f) (per gc-3moew.7) Original commit's intent ported to post-upstream code in the shared rebase worktree. See gc-3moew.7 for context and metadata.classification. Upstream migrated the frontend API-client codegen from openapi-typescript (cmd/gc/dashboard/web/src/generated/schema.d.ts) to @hey-api/openapi-ts (internal/api/dashboardspa/.../gc-supervisor-client/). The obsolete schema.d.ts is dropped; input_tokens is ported to the new generated types.gen.ts (rename-applied) and zod.gen.ts, matching the openapi change.
…ons Huma handler into response_cache (gc-6y7ril) (per gc-9n4v5n.8) (per gc-vtpf5.2) (per gc-3moew.8) Original commit's intent ported to post-upstream code in the shared rebase worktree. See gc-3moew.8 for context and metadata.classification.
…bundled order (gc-517n7q) (per gc-9n4v5n.9) (per gc-vtpf5.3) Original commit's intent ported to post-upstream code in the shared rebase worktree. See gc-vtpf5.3 for context and metadata.classification. Rework details (mechanical): - internal/builtinpacks/registry_test.go: union resolution. Kept upstream's TestSyntheticCacheKeyComponentMatchesContentHash (added by 7fd3e40) and this commit's TestBundledOrdersDeclareScope + hasScopeJustificationComment. Both are independent end-of-file additions; no semantic overlap. - examples/gastown/packs/maintenance/orders/nudge-mail-sweep.toml: new upstream order. Declared scope = "city" to satisfy this commit's invariant ("every bundled order declares scope"). Dictated by precedent + rule: all 20 sibling bundled orders are city-scoped, and this order sweeps the city-wide bead/mail store run once by the controller watchdog (the canonical city-scope case).
Upstream test helper testControlDispatcherAgentTOML used b.WriteString(fmt.Sprintf(...)), which trips the fork's strict staticcheck QF1012 config when replayed onto e862be9. Switch to the staticcheck-recommended fmt.Fprintf(&b, ...) — behavior-identical. Mirrors prior fork lint-fallout cleanup (dd47164).
…in (gc-o4o10) (#84) (per gc-vjoy6.14) Original commit's intent ported to post-upstream code in the shared rebase worktree. See gc-vjoy6.14 for context and metadata.classification.
…el IDs (gc-os8fn) (#85) ModelContextWindow returned the 1M window only when the model ID carried the literal "[1m]" suffix. The provider echoes modern Claude models back without that launch flag (e.g. "claude-opus-4-8"), so the API/session-log path fell through to the 200K default and context_pct saturated at 100% — a deacon at 383K on a real 1M window read as 100% instead of ~38%. The sibling resolver in cmd/gc/context_inject.go already classified bare opus-4-6/4-7/4-8, sonnet-4-6, fable, and mythos as 1M; the two resolvers had drifted and the broken one fed the API. Extract the single source of truth into internal/modelwindow.Window and delegate both call sites to it so they can no longer disagree. A new neutral package is required because the worker-boundary import test forbids non-test cmd/gc files from importing internal/sessionlog. - bare claude-opus-4-8 (and 4-6/4-7, sonnet-4-6, fable, mythos) -> 1M - the "[1m]" suffix still forces 1M for any Claude family - older/unknown Claude families keep the 200K default - unknown families return 0 so each caller applies its own policy (session-log/API: window undetermined; injector: floor to 200K)
…5-arg signature (cityExplicit)
…age (fork invariant)
…gc-yey06) (#88) Only unassigned routed work is pool demand, so a bare 'gc sling <pool> <bead>' of an existing open bead must clear a stale assignee or the re-pour is invisible to scale_check and stalls silently. In-progress beads are exempt. The order-sweep rig-dir stat gate that originally rode along in this commit was dropped: all configured rigs exist on disk, so it guarded a condition that does not occur, at the cost of threading an io.Writer through 3 call sites.
…olated git config (gc-v2z1p) (#89) `make check` runs `go test -p=4 ./...`; the parallel link phase of several large CGO/ICU test binaries peaks past the 16G tmpfs-backed /tmp and fails mid-link with "No space left on device". This surfaces as spurious rebase/refinery preflight failures (first hit hard by the gc-k0hvd upstream-rebase polecat, which worked around it by pointing TMPDIR at ext4). The flake is latent and recurs under load whenever /tmp is tight. Root cause: the TEST_ENV `env -i` wrapper passed TMPDIR through with a /tmp default, and GOTMPDIR is empty, so the Go toolchain's build/link work dir (go-build*/go-link*) and test-runtime temp both land on the tmpfs. Fix: default the wrapper's TMPDIR to /var/tmp (a persistent, non-tmpfs disk with more room; ext4 with 66G free on the Gas City host) instead of /tmp. Every `make test*` target flows through TEST_ENV, so the shard/parallel helper scripts it invokes inherit the same TMPDIR — one leverage point covers all preflight paths. An explicit TMPDIR export still overrides (e.g. `TMPDIR=/tmp make check`), so callers that want the old behavior keep it. Validation (fixed env, TMPDIR unset): - `go build -work` reports WORK=/var/tmp/go-build... (was /tmp) - a built binary sees os.TempDir()=/var/tmp - with TMPDIR=/tmp exported, WORK returns to /tmp/go-build... (override preserved) - `make -n test` expands the recipe with TMPDIR="${TMPDIR:-/var/tmp}" Environment/infra fix, not a code regression. No behavior change for tests themselves — they use whatever os.TempDir() returns. Second host-state leak, same wrapper: TEST_ENV allowlists HOME but not SSH_AUTH_SOCK, so a host ~/.gitconfig with commit.gpgsign=true plus gpg.format=ssh made every test that execs `git commit` fail with "Couldn't get agent socket?" / "failed to write commit object". That was being patched per-test by sprinkling testutil.IsolatedGitConfig(t) into upstream-owned test files — carry that grew with every new upstream test that execs git. Fix: point GIT_CONFIG_GLOBAL at a seeded config and GIT_CONFIG_SYSTEM at /dev/null in the same wrapper, so every test binary inherits the isolation and no per-test opt-in is needed. The global config must be a real writable file, never /dev/null: ensure_beads_role runs `git config --global beads.role maintainer` and cannot lock /dev/null. Tests that WRITE global config still call testutil.IsolatedGitConfig for a per-test file so writes don't leak through the shared one, and helpers that build an explicit env -i subprocess environment still append SharedIsolatedGitConfigEnv.
…91) Motivation: `make check` (`go test -p=4 ./...`) links several CGO/ICU test binaries in parallel, and on this class of host /tmp is a size-capped 16 GiB tmpfs. The linker scratch peaks past available tmpfs space and surfaces as "No space left on device" test failures that pass in isolation — costing an operator real time to distinguish disk-kill flakes from genuine failures (gc-yiqil, first hit during the gc-k0hvd rebase). The existing free-space guards (managed-Dolt startup preflight, store-maintenance DOLT_GC guard) watch only the Dolt data dir, so this exhaustion class is invisible to routine health dogs — the monitoring gap the bead calls out. Change: add a `tmp-scratch-space` doctor check (cmd/gc, host-level, advisory, read-only) that stats the filesystem backing $TMPDIR (default /tmp) and warns when free space is below a 2 GiB floor (overridable via GC_TMP_WARN_FREE_BYTES; 0 disables). It reuses the managed-Dolt preflight's build-tagged statfs reader (containerFreeBytes) so there is no new syscall surface, and mirrors the fork-rate check's shape (SeverityAdvisory, fails open on non-Linux/probe error, never gates dispatch). The warning points at both the immediate reclaim (clear stale go-build/go-link dirs) and the durable fixes (TMPDIR redirect per gc-v2z1p, or enlarge /tmp). Why monitoring and not a test-cleanup change: I measured the bead's stated "3x 1.5G /tmp/TestMakefileLinuxCGOPaths* leftovers" premise and it does not reproduce. The six TestMakefileLinuxCGOPaths* tests use t.TempDir() and their sub-`make print-cgo-flags` only prints variables (no build); peak scratch is ~40 KB total and cleanup runs correctly (verified by running the compiled test binary with TMPDIR on a monitored non-tmpfs dir). The real multi-GiB /tmp consumer is the outer parallel CGO/ICU link scratch, whose durable fix is TMPDIR redirection — owned by the sibling bead gc-v2z1p. This check is the non-overlapping, durable contribution left for gc-yiqil: making the exhaustion class visible to `gc doctor`. Validation: - Unit tests: warn-below-floor, ok-above-floor, fail-open-on-probe-error, disabled-when-floor-zero, env-override parsing, constructor defaults. - Updated the golden doctor check-name set (registered after fork-rate). - End-to-end `gc doctor` run: OK branch reports real /tmp free (8.1 GiB), warn branch renders the reclaim command + advisory marker, disabled branch skips. - gofmt, go vet, and golangci-lint (--new-from-rev=origin/main) all clean. Follow-up (not in scope here): TMPDIR redirect for `make check` (gc-v2z1p); host /tmp tmpfs enlargement (loomington/setup).
…#92) Gate the fork's zookanalytics/beads pseudo-version replace past the check-gomod-replace guard so Preflight/static-checks runs to completion and fork PRs can auto-land again (was FAILURE-at-guard on main). - gc-jwho8: allowlist the beads fork entry in scripts/check-gomod-replace.sh - gc-434qa (pre-open signoff): gate the allowlist on pseudo-version shape so the exception stays narrow, plus scripts/gomod_replace_guard_test.go coverage The FORK_REPLACE_ALLOWLIST entry is a TEMPORARY bridge: delete it when the beads fork converges upstream and go.mod's `replace` returns to a released semver tag. Closes gc-bvjbs.
These non-conflicted cmd/gc test files kept pre-rebase call shapes and only surfaced post-rebase (cmd/gc could not build mid-rebase until the resolveBdScopeTarget caller fix landed): - startControllerSocket gained demandDirty (gc-qedgc #69) — add missing nil arg - startCandidate's raw bead pointer became info session.Info (WI-6 R4): session:&bead -> info: SeedBead / InfoFromMeta - sortCandidatesByWakeFairness gained a cfg param
…, resource census) (gc-u1g6k) - import_trust_test.go: disable commit/tag signing in the fixture repo so the parallel test subprocess does not require an unreachable ssh agent (ref tk-9zgnf). - gc_env_read_baseline.golden: add GC_TMP_WARN_FREE_BYTES (new env from gc-yiqil). - resource census: ratchet baselines up to current AST counts across bootstrapPolicy, test/test-resources.toml, and the TESTING.md ledger block.
… (gc-uz8az) .githooks/pre-push is the repo's designated push-time test gate (gastownhall#3628 moved the heavy `go test` fan-out off pre-commit onto push). It derived `go_changed` from git's per-ref lines read on stdin and, if it read ZERO lines, left go_changed=0 and `exit 0` immediately — emitting nothing. That degraded the gate to a silent no-op indistinguishable, from the pusher's side, from "tests ran and passed": a 581-file upstream-rebase push once landed on main in 3s with no hook output and the suite never ran, while the push looked clean. Fix, distinguishing the two states the old code conflated: - "read 0 refs" (degenerate/anomalous — git feeds one line per pushed ref, so zero lines means the gate cannot see what is being pushed): FAIL CLOSED. Count refs_seen; when it is 0, print a diagnostic to stderr and run the suite rather than silently passing an unexamined push. - "read N refs, none touched .go" (legitimate skip): make the skip LOUD. Print a visible "skipping test suite" line before exit 0 so an absent test run is always noticeable in push output, never silent. refs_seen uses `x=$((...))` assignment form (not bare `((...))`), which is safe under `set -e` regardless of the counter value. Validation: added scripts/prepush_contract_test.go — a behavioral test that runs the actual hook under bash with a PATH-stubbed `make` (mirroring the existing precommit_contract_test.go pattern) and asserts on a make call log. The two failing cases (empty stdin -> suite runs; no-.go diff -> loud skip) now pass, and a real .go-change diff still runs the suite. Rebased onto post-upstream-rebase main. Upstream reworded the hook's header comment and replaced the `LOCAL_TEST_JOBS` export with the shared scripts/test-local-job-count policy; both are preserved verbatim here. Upstream did NOT absorb this bug — the silent `exit 0` path is still present on new main — so the commit's intent is reworked onto the new content rather than dropped. Upstream's own TestPrePushUsesCanonicalMachineAwareConcurrency passes against the rebased hook, confirming the two changes compose. Resource-census ratchet (test/test-resources.toml + bootstrapPolicy + TESTING.md) The new test file adds one subprocess call site (the `bash` invocation that runs the real hook — irreducible: executing the hook IS the behaviour under test) and one file, so the three checked subprocess rows move by +1 call / +1 file: audit_baseline scope=all 535/156 -> 536/157 debt scope=untagged 408/110 -> 409/111 small_debt scope=untagged 406/109 -> 407/110 Growth was first halved before ratcheting: the test originally carried its own `gitCapture` helper, a near-duplicate of the existing `runGit` in the same package. `runGit` now returns its trimmed output so callers needing a value (`rev-parse HEAD`) can read it, and gitCapture is deleted — removing the second new call site (+2 -> +1) and the duplication. Existing runGit callers are unaffected; they ignore the new return value. The remaining +1/+1 cannot be removed without dropping the test. Per the manifest header, the ledger is pinned in three places that must move together, and all three are updated here: internal/testpolicy/resourcecensus/census.go (bootstrapPolicy), test/test-resources.toml, and the generated TESTING.md table. The Medium-owner exemption does not apply: it only covers calls lexically inside the named top-level test, and this call site lives in a helper. Note for review: the debt/small_debt rows carry "cannot grow" invariants, so this ratchet is a deliberate, reviewable increase rather than routine bookkeeping. gofmt and `go vet ./scripts/ ./internal/testpolicy/...` clean; ./scripts and ./internal/testpolicy/... pass.
…-uz8az)
The gate had a second fail-open path, distinct from the no-refs exit-0 fixed
in the previous commit and found only because that commit made the skip loud.
go_changed was derived with:
git diff --name-only "$remote_sha" "$local_sha" -- '*.go' | grep -q .
under `set -o pipefail`. `grep -q` exits on its first match and closes the
pipe, so `git diff` is killed by SIGPIPE (141) whenever it is still writing.
pipefail then reports 141 for the whole pipeline, the `if` never fires, and
go_changed stays 0 -- the gate skips the suite on a diff that DID touch .go
files. Whether git finishes writing first is a race on diff size, so the gate
skipped nondeterministically: 4 of 8 runs on one real 913-file commit pair,
and one live 913-file force-push that skipped the suite entirely.
Use `git diff --quiet`, which reports via exit status and produces no output,
so there is no pipe and no SIGPIPE. Treating any non-zero status as "changed"
also fails closed when the revision cannot be read, matching the fail-closed
posture of the no-refs path.
The regression test drives a diff whose file list exceeds the 64 KiB pipe
buffer, which turns the race into a deterministic failure against the piped
implementation, and repeats it to catch a partially-restored pipeline that
only flakes. The shared fixture now disables git auto-maintenance so the
larger repo does not race t.TempDir cleanup.
b550924 to
faa5da3
Compare
Rebased onto rewritten
|
zook-bot
left a comment
There was a problem hiding this comment.
Codex signoff (post-rebase, comment-only; blocking changes required):
Verdict: REQUEST_CHANGES
Reviewed PR: #93
Reviewed branch/head: polecat/gc-uz8az @ faa5da3
Target branch: main
Blocking finding:
- The branch needs another rebase before codex can sign off. During this review,
mainadvanced to18fdb682d0c3ff4b4d3cd0efa84f91287792a29f; GitHub now reportsmergeStateStatus=DIRTY, andgit diff origin/main...refs/review/pr-93-headexpands to 69 files instead of the intended six-file PR surface. The reviewed branch content may still be fine, but the current merge candidate is stale and not the diff this gate should certify.
What I did verify on the intended hook/resource-ledger surface:
.githooks/pre-pushnow counts stdin ref lines and fails closed by running the suite when it reads zero refs.- The legitimate no-Go-change path is loud.
- The
.godetector usesgit diff --quietinstead of the SIGPIPE-pronegit diff --name-only | grep -qpipeline. - The new contract tests cover empty stdin, loud no-Go skip, normal Go changes, and a large Go diff.
- The +1 subprocess resource-census ratchet is consistent across
internal/testpolicy/resourcecensus/census.go,test/test-resources.toml, andTESTING.md.
Validation run at reviewed head:
bash -n .githooks/pre-pushpassed.TMPDIR=/var/tmp/... go test ./scripts -run TestPrePush -count=1 -vpassed.TMPDIR=/var/tmp/... go test ./scripts -count=1passed.TMPDIR=/var/tmp/... go vet ./scripts/... ./internal/testpolicy/...passed.git diff --check origin/main refs/review/pr-93-headpassed before the base advanced check.TMPDIR=/var/tmp/... go test ./internal/testpolicy/... -count=1fails only on thecmd/gc+untagged environmentcensus drift; I reproduced the same failure on the base checkout, so that part is not branch-attributable.
Required next step: rebase polecat/gc-uz8az onto current main, push with force-with-lease, and re-run codex signoff on the new head.
7bba3de to
89e2e69
Compare
|
Closing as obsolete per fork-shed evaluation (2026-07-23). |
Summary
.githooks/pre-pushis the repo's designated push-time test gate (gastownhall#3628 moved theheavy
go testfan-out off pre-commit and onto push). It has a path where it exits0 having run NOTHING, and prints NOTHING while doing so — so the gate fails OPEN and
silently. A push carrying hundreds of changed
.gofiles can land with the suitenever having run, and nothing in the push output says the tests were skipped.
Observed
Landing the gc-vjoy6 upstream rebase (force-with-lease of
rebase/gc-vjoy6->main, 581 changed.gofiles between the old tip and the new one), the pushcompleted in 3 seconds with zero hook output:
make test-fast-parallelcannot run in 3s, and it would have echoed output. Itnever ran.
Root cause: the silent exit-0 path
The hook derives
go_changedby reading git's ref lines from stdin:If the hook reads no ref lines,
go_changedstays 0 and it exits 0 immediately,emitting nothing. Reproduced directly:
So any condition that leaves the hook's stdin empty degrades the gate to a no-op
that is indistinguishable, from the pusher's side, from "tests ran and passed."
Ruled out as causes: hook is executable (
-rwxrwxr-x);core.hooksPath=.githooksresolves (
git rev-parse --git-path hooks/pre-push->.githooks/pre-push); the.godiff is non-empty (581 files); noGIT_*env suppression (onlyGIT_EDITOR);git 2.55.0. Push was NOT run with
--no-verify.Why it matters
Every polecat and refinery push relies on this gate. Failing open means a red suite
can land on
mainwhile the push looks clean. The failure is invisible: there is no"no Go changes, skipping tests" line to notice its absence.
Fix candidates
echo "pre-push: no Go changes -- skipping suite"beforeexit 0, so a skipped gate is always visible in push output.(distinct from "ref lines seen, no .go changes"), that is not a legitimate
"nothing changed" -- treat it as an error and run the suite (or exit non-zero)
rather than silently passing.
Distinguish the two states: "read N refs, none touched .go" (legitimately skip) vs
"read 0 refs" (something is wrong).
Note on the gc-vjoy6 landing
The landed code was NOT ungated: the refinery ran a fresh full
make checkat exactlythe landed sha 50a3822 (fmt-check + lint under the pinned golangci-lint 2.9.0 + vet +
check-routed-test-rows +
go test ./...), all green, before pushing.make test-fast-parallelis a subset of that. This bead is about the gate itself, notthat landing.
Filed by the refinery (merge processor) rather than fixed in place -- editing the repo's
hooks is developer work.
Implementation notes
Implemented: pre-push gate fails closed on a no-refs stdin read (runs the suite instead of a silent exit 0) and skips loudly when no Go changes; added regression test scripts/prepush_contract_test.go, made hermetic (commit.gpgsign=false in the fixture) so it passes under the hook's env -i where SSH signing has no agent. Pre-push suite: all fast jobs passed.
Refinery handoff
gc-uz8az(bug, P1)polecat/gc-uz8azmain0c6a1b26; PR opened codex-green.