Skip to content

v1.91.113.0 fix(capy): trailers say behind=<n> with a fix, a skipped upgrade is never ok, and a reused pid no longer holds the upgrade lease - #3232

Merged
capy-ai[bot] merged 15 commits into
mainfrom
capy/fl2-F1
Oct 17, 2026
Merged

capy-ai[bot] merged 15 commits into
mainfrom
capy/fl2-F1

Conversation

@garrytan

@garrytan garrytan commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Fast track from field list 2: item 13 (installs fall behind without saying so) plus two S1 reports from the field, GBRA-105 and GBRA-106 (a session-start upgrade wedged by a reused pid and still reported ok).

A stale install now shows in the trailer

Lanes relay only the gstack: trailer. --check already printed gstack-capy-session: behind origin/<branch>: … fix: …, but on its own line, so every handoff lost it. Now, whenever a session ends behind origin's default branch, bin/gstack-capy-session exports the gap (GSTACK_CAPY_BEHIND, _BEHIND_TO, _BEHIND_FIX), and bin/gstack-capy-install writes it into the ok trailer:

gstack: ok 1.91.101.0 for=autoplan (behind=12 (latest: 1.91.107.0); fix: bash <skill>/scripts/install.sh --for autoplan)
  • The count comes from this run's fetch (FETCH_HEAD), so it works with the wrapper-1 Drive copy every crew machine runs today. It replaces the latest: note rather than appearing next to it.
  • The fix is the command that actually closes the gap: the rerun when it would fast-forward; <root>/bin/gstack-upgrade for a dirty, detached, non-default-branch or diverged checkout (it names its own refusal); unset <VAR>, then … when upgrades are switched off. It never contains the ; note separator. The session's own behind origin line now prints the same fix.
  • The capy skill and docs/capy-install.md treat behind= as "run the fix".

A skipped upgrade is never ok

GBRA-106's install.sh (install mode) printed ok 1.91.101.0 and exited 0 without upgrading or saying why. Now, when the session-start self-upgrade is skipped (busy, refused, or gstack-upgrade missing), the trailer is:

gstack: fail behind — installed <rev> is <n> commits behind origin (latest <ver>) and the session-start upgrade was skipped: <reason>; fix: <command> (UPGRADE_BUSY|UPGRADE_REFUSED)

and the exit status is 1, so the install verdict refuses workflows until the fix runs. An explicit opt-out (GSTACK_CAPY_SELF_UPGRADE=off or GSTACK_CAPY_AUTO_UPGRADE=0) stays ok, with the behind= note. --check still never moves the checkout and stays ok with behind=.

A reused pid no longer holds the lease

GBRA-105's lease named pid 379, which after a machine wake was dbus-daemon. GBRA-106's named pid 357, which had become a 49-second-old systemd-logind. Every upgrade then refused with UPGRADE_BUSY until someone set GSTACK_UPGRADE_FORCE=1. bin/gstack-lease touch now records the holder's start identity: boot id plus the /proc start time on Linux, ps -o lstart elsewhere. When the recorded identity doesn't match the current process, the lease is stale, and live reclaims it with a gstack-lease: reclaimed stale lease … line. A lease without an identity (written by an older gstack) goes stale after the 120-minute prune window. live names each holder as pid <p> (<age>m <command>), so UPGRADE_BUSY's message and the fail behind trailer show who holds the lease. list prints alive|dead|stale.

Lease safety (after review)

A stale verdict needs proof: two well-formed identities from the same backend that differ. An identity that can't be read right now, a malformed record, or a backend change keeps the lease, because a live session is never reclaimed without evidence. The ps fallback reads lstart under LC_ALL=C TZ=UTC0, so it doesn't depend on the caller's timezone or locale.

touch (publish by temp file plus rename, prune) and reclaim (reread, then delete only the record judged stale) run under one lock in the lease directory: a symlink .lock whose target names its holder's pid and start identity, created atomically with ln -s. The lock is broken only when its holder is dead or its pid was reused, never by age. Breakers serialize on a second symlink, .lock.break, and reread .lock before removing it, and unlock removes only the caller's own lock. While a live process holds the lock, touch exits 3 without writing (gstack-skill-start reports that the session holds no workflow lease) and a reclaim keeps the lease.

Known limit: clearing a dead .lock.break is not atomic, so two gstack-lease processes killed in a row (one holding .lock, the next while breaking it) can still let a reclaim delete a renewal. A single crash is always safe. The bin comment and docs/capy-install.md say this. Out of scope here: bin/gstack-upgrade's own window between gstack-lease live and the fast-forward. That file is #3225's, and the window already existed before this PR.

Tests

  • test/capy-skill-wrapper.test.ts: --check behind (clean, dirty, detached, topic branch, disabled); install mode skipped or disabled; a live holder blocking with UPGRADE_BUSY and the holder named; a reused pid reclaimed so the checkout upgrades; current and offline sessions exporting nothing, inherited values dropped.
  • test/gstack-capy-install.test.ts: trailer grammar and order, and the fail behind conversion with exit 1.
  • test/gstack-lease.test.ts: reused-identity reclaim and legacy-lease aging.
  • Two existing assertions changed with the contract: the lease live line now ends in the holder's command (gstack-lease and gstack-upgrade-bin tests).

test/gstack-lease.test.ts also covers the review findings with BASH_ENV fault injection: an unreadable /proc and ps, malformed and other-backend records, a timezone and locale change, a renewal racing a reclaim, a dead or reused-pid lock holder, a live holder's aged lock (never broken), two breakers of one dead lock, a dead .lock.break, unlock leaving a lock that changed hands, and gstack-skill-start's no-lease report. Mutation checks confirm the lock tests fail when the reread, the ownership check or the liveness check is removed. Focused run on this tree (main 86091dd merged): 377 pass, 0 fail across 12 files. Typecheck is clean. Five review rounds ended at 0 P0, 0 P1, 0 P2, 1 P3 (the known limit above).

Gate repair

The paid gate at 641916c failed tpa-absent-linux on a detector false positive. A Defer option reading "I set this aside … no browsing now" matched the Aside drive-offer check, which matched aside case-insensitively. test/helpers/third-party-actions.ts now matches the product name Aside only. The captured answer is a free regression in test/third-party-actions.test.ts, and a mutation check fails it when the old pattern is restored. At c7a2983 the gate is GREEN: rule 108/108, judge 24/24, $89.82 known.

Release note for the stamp

At stamp time, capy/support.env GSTACK_MIN_REVISION goes up to this PR's own version. This is an install-honesty fix, which is the documented rule, so older checkouts get fail revision with a fix: line at --check and are fast-forwarded at install-mode session start.

Refs #2378 (its Capy half; the Claude Code update-check half is planned as W12 in the field list 2 wave).

Open in Capy

garrytan and others added 3 commits October 16, 2026 23:10
…e install shows in every relayed trailer (field list 2 item 13, #2378)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ed after a wake is reclaimed; a skipped session-start upgrade ends fail behind, never ok (GBRA-105, GBRA-106)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@trunk-io

trunk-io Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

😎 This pull request was merged.

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

E2E Evals: ✅ PASS

[test:paid] VERDICT GREEN — lane gate/pr, attempt 1
  rule 108/108 · behavior 0/0 · judge 24/24 · quarantined 0 (0 failing the lane)
  SKIPPED 0 · INFRA 0 · INCOMPLETE 0 · unattributed 0 · ACTION REQUIRED 0
  wall 9m47s · cost $92.99 known + 54 of 132 trial(s) cost unknown

121 executed, 0 reused rule/judge records | 0 manual accepted (unscored; no score-cache credit) | $92.99 rule/judge cost | reconcile exit: 0

Profile: pr / mode full-fallback; selected behaviors: 108, judges: 24. Deferred to scheduled/release coverage: 137 behaviors and 0 changed prompt files. Deferred checks did not run and receive no PR-pass credit.

Full gate restored by:

  • capy/support.env (real unknown dependency): register capy/support.env under the cases that consume it in test/helpers/touchfiles-data.ts, add it to FREE_ONLY_PR_FILES in scripts/test-pr-profile.ts if no paid case reads it, or add its directory to DERIVABLE_PREFIXES so scripts/pr-dependencies.ts derives its consumers

Derived dependents (scripts/pr-dependencies.ts):

  • bin/gstack-capy-install: 244 case(s), 24 judge(s)
  • bin/gstack-lease: 244 case(s), 24 judge(s)
Rule and judge shards
Shard Automated result Manual/unscored Executed Reused Status Cost
e2e/skill-e2e-workflow 4/4 0 4 0 ✅ $3.11
e2e-deploy/skill-e2e-deploy--land-and-deploy-workflow 1/1 0 1 0 ✅ $1.94
e2e-safety-codex-consult/skill-e2e-safety-codex-consult 1/1 0 1 0 ✅ $0.96
e2e-ship-docsync/skill-e2e-ship-docsync--ship-docsync-stale-before 1/1 0 1 0 ✅ $1.14
e2e-review-attribution/skill-e2e-review-attribution 3/3 0 3 0 ✅ $0.98
e2e/skill-e2e-shared-libs--shared-libs-review-lifecycle 1/1 0 1 0 ✅ $2.44
e2e-qa-workflow/skill-e2e-qa-workflow--qa-quick 1/1 0 1 0 ✅ $1.67
e2e/skill-e2e-shared-libs-paths--shared-libs-review-prior-coverage 1/1 0 1 0 ✅ $2.58
e2e/skill-e2e-ship-skip 1/1 0 1 0 ✅ $0.64
e2e-qa-callers/skill-e2e-qa-callers--review-exploratory-small-cli 1/1 0 1 0 ✅ $1.83
e2e/skill-e2e-test-value 3/3 0 3 0 ✅ $2.67
e2e-qa-workflow/skill-e2e-qa-workflow--qa-only-no-fix 1/1 0 1 0 ✅ $1.62
e2e-qa-callers/skill-e2e-qa-callers--ship-exploratory-plan-checks 1/1 0 1 0 ✅ $2.07
e2e/skill-e2e-shared-libs--shared-libs-review-revalidation 1/1 0 1 0 ✅ $5.01
e2e-ship-docsync/skill-e2e-ship-docsync--ship-docsync-recovery 1/1 0 1 0 ✅ $0.93
e2e-review/skill-e2e-review 2/2 0 2 0 ✅ $1.18
e2e-ship-docsync/skill-e2e-ship-docsync--ship-docsync-failure 1/1 0 1 0 ✅ $1.95
e2e-skillify/skill-e2e-skillify 3/3 0 3 0 ✅ $2.4
e2e/skill-e2e-shared-libs-paths--shared-libs-review-path-eligibility 1/1 0 1 0 ✅ $3.94
e2e-ship-docsync/skill-e2e-ship-docsync--ship-docsync-current 1/1 0 1 0 ✅ $3.36
llm-judge/skill-llm-eval 24/24 0 24 0 ✅ $1.44
e2e/skill-e2e-coverage-audit 2/2 0 2 0 ✅ $0.77
e2e-deploy/skill-e2e-deploy--setup-deploy-workflow 1/1 0 1 0 ✅ $0.36
e2e-review-army/skill-e2e-review-army--review-army-perf-n-plus-one 1/1 0 1 0 ✅ $1.08
e2e/skill-e2e-shared-libs-paths--shared-libs-review-index-flags 1/1 0 1 0 ✅ $2.42
e2e/skill-e2e-investigate-owned-completion 1/1 0 1 0 ✅ $0.2
e2e-ship-docsync/skill-e2e-ship-docsync--ship-docsync-late-result 1/1 0 1 0 ✅ $0.93
e2e-review-army/skill-e2e-review-army--review-army-json-findings 1/1 0 1 0 ✅ $0.22
e2e-deploy/skill-e2e-deploy--benchmark-workflow 1/1 0 1 0 ✅ $0.7
e2e-qa-callers/skill-e2e-qa-callers--ship-exploratory-small-cli 1/1 0 1 0 ✅ $1.82
e2e-learnings/skill-e2e-learnings 1/1 0 1 0 ✅ $0.4
e2e-deploy/skill-e2e-deploy--land-and-deploy-review-gate 1/1 0 1 0 ✅ $1.18
e2e-review-army/skill-e2e-review-army--review-army-migration-safety 1/1 0 1 0 ✅ $1.17
e2e/skill-e2e-shared-libs--shared-libs-read-only 1/1 0 1 0 ✅ $0.82
e2e-ship-docsync/skill-e2e-ship-docsync--ship-docsync-stale-after 1/1 0 1 0 ✅ $1.1
e2e-deploy/skill-e2e-deploy--land-and-deploy-first-run 1/1 0 1 0 ✅ $1.12
e2e-docsync-spawned/skill-e2e-docsync-spawned 1/1 0 1 0 ✅ $1.3
e2e-qa-functional-fix/skill-e2e-qa-functional-fix 2/2 0 2 0 ✅ $2.87
e2e/skill-e2e-plan--plan-review-report 1/1 0 1 0 ✅ $1.06
e2e-qa-callers/skill-e2e-qa-callers--ship-exploratory-unavailable 1/1 0 1 0 ✅ $2.01
e2e-retro/skill-e2e-retro 1/1 0 1 0 ✅ $1.07
e2e-qa-functional/skill-e2e-qa-functional 2/2 0 2 0 ✅ $2.52
e2e-qa-callers/skill-e2e-qa-callers--ship-exploratory-late-input 1/1 0 1 0 ✅ $2.25
e2e-cso/skill-e2e-cso 1/1 0 1 0 ✅ $1.28
e2e-ship-docsync/skill-e2e-ship-docsync--ship-docsync-store 1/1 0 1 0 ✅ $3.56
e2e-ship-docsync/skill-e2e-ship-docsync--ship-docsync-completion 1/1 0 1 0 ✅ $3.58
e2e-design/skill-e2e-design--design-review-detector-shim-dom 1/1 0 1 0 ✅ $0.68
e2e/skill-e2e-shared-libs--shared-libs-unsupported-git 1/1 0 1 0 ✅ $0.68
e2e-ship-docsync/skill-e2e-ship-docsync--ship-docsync-missing-asset 1/1 0 1 0 ✅ $1.26
e2e-review-army/skill-e2e-review-army--review-army-quality-score 1/1 0 1 0 ✅ $0.36
e2e-safety-ship-evidence/skill-e2e-safety-ship-evidence 1/1 0 1 0 ✅ $0.75
e2e-hermetic/skill-e2e-hermetic-canary 2/2 0 2 0 ✅ $0.02
e2e-design/skill-e2e-design--design-review-plugin-handoff 1/1 0 1 0 ✅ $0.47
e2e-ship-docsync/skill-e2e-ship-docsync--ship-docsync-missing-marker 1/1 0 1 0 ✅ $0.51
e2e-browse/skill-e2e-bws 6/6 0 6 0 ✅ $0.85
e2e-third-party-actions/skill-e2e-third-party-actions 5/5 0 5 0 ✅ $1.23
e2e-plan-tune/skill-e2e-plan-tune 1/1 0 1 0 ✅ $0.5
e2e-session-intelligence/skill-e2e-session-intelligence 4/4 0 4 0 ✅ $0.83
e2e-safety-pair-agent/skill-e2e-safety-pair-agent 1/1 0 1 0 ✅ $0.3
e2e-design/skill-e2e-design--design-review-detector-shim 1/1 0 1 0 ✅ $0.4
e2e-qa-workflow/skill-e2e-qa-workflow--qa-bootstrap 1/1 0 1 0 ✅ $0.23
e2e-triage/skill-e2e-triage 1/1 0 1 0 ✅ $0.5
e2e/skill-e2e-ship-hook-consent 2/2 0 2 0 ✅ $0.68
e2e-review-army/skill-e2e-review-army--review-army-delivery-audit 1/1 0 1 0 ✅ $0.35
e2e-ship-docsync/skill-e2e-ship-docsync--ship-docsync-timeout-unsettled 1/1 0 1 0 ✅ $0.51
e2e/skill-e2e-ship-hook-refresh 1/1 0 1 0 ✅ $0.28
e2e/skill-e2e-investigate-owned-termination 2/2 0 2 0 ✅ $0.54
e2e-ship-docsync/skill-e2e-ship-docsync--ship-docsync-launch-failure 1/1 0 1 0 ✅ $0.45
e2e-deploy/skill-e2e-deploy--canary-workflow 1/1 0 1 0 ✅ $0.35
e2e-design/skill-e2e-design--plan-design-review-no-ui-scope 1/1 0 1 0 ✅ $0.61
Fail-closed reconciliation
2s  test/skill-e2e-qa-callers.test.ts#ship-exploratory-small-cli
  slice 12  passed            135s  test/skill-e2e-shared-libs-paths.test.ts#shared-libs-review-index-flags
  slice 12  passed            112s  test/skill-e2e-ship-docsync.test.ts#ship-docsync-late-result
  slice 12  passed             98s  test/skill-e2e-deploy.test.ts#benchmark-workflow
  slice 12  passed            133s  test/skill-e2e-review-army.test.ts#review-army-perf-n-plus-one
  slice 12  passed             31s  test/skill-e2e-plan-ceo-plan-mode.test.ts
  slice 12  passed             26s  test/skill-e2e-investigate-owned-completion.test.ts
  slice 12  passed             27s  test/skill-e2e-review-army.test.ts#review-army-json-findings
  slice 13  passed            109s  test/skill-e2e-ship-docsync.test.ts#ship-docsync-missing-asset
  slice 13  passed             85s  test/skill-e2e-plan-devex-plan-mode.test.ts
  slice 13  passed             97s  test/skill-e2e-safety-ship-evidence.test.ts
  slice 13  passed            102s  test/skill-e2e-design.test.ts#design-review-detector-shim-dom
  slice 13  passed            111s  test/skill-e2e-shared-libs.test.ts#shared-libs-unsupported-git
  slice 13  passed             69s  test/skill-e2e-office-hours-auto-mode.test.ts
  slice 13  passed             71s  test/skill-e2e-design.test.ts#design-review-plugin-handoff
  slice 13  passed             61s  test/skill-e2e-bws.test.ts
  slice 13  passed             63s  test/skill-e2e-ship-docsync.test.ts#ship-docsync-missing-marker
  slice 13  passed             50s  test/skill-e2e-review-army.test.ts#review-army-quality-score
  slice 13  passed              6s  test/skill-e2e-hermetic-canary.test.ts
  slice 14  passed             85s  test/skill-e2e-third-party-actions.test.ts
  slice 14  passed             69s  test/skill-e2e-plan-tune.test.ts
  slice 14  passed             64s  test/skill-e2e-triage.test.ts
  slice 14  passed             64s  test/skill-e2e-ship-docsync.test.ts#ship-docsync-timeout-unsettled
  slice 14  passed             54s  test/skill-e2e-ask-user-question-format-compliance.test.ts
  slice 14  passed             52s  test/skill-e2e-ship-docsync.test.ts#ship-docsync-launch-failure
  slice 14  passed             49s  test/skill-e2e-session-intelligence.test.ts
  slice 14  passed             49s  test/skill-e2e-ship-hook-consent.test.ts
  slice 14  passed             49s  test/skill-e2e-investigate-owned-termination.test.ts
  slice 14  passed             50s  test/skill-e2e-design.test.ts#design-review-detector-shim
  slice 14  passed             33s  test/skill-e2e-deploy.test.ts#canary-workflow
  slice 14  passed             44s  test/skill-e2e-ship-hook-refresh.test.ts
  slice 14  passed             55s  test/skill-e2e-design.test.ts#plan-design-review-no-ui-scope
  slice 14  passed             34s  test/skill-e2e-review-army.test.ts#review-army-delivery-audit
  slice 14  passed             29s  test/skill-e2e-safety-pair-agent.test.ts
  slice 14  passed             28s  test/skill-e2e-qa-workflow.test.ts#qa-bootstrap
  slice 15  passed             29s  test/skill-e2e-learnings.test.ts
  slice 15  passed             28s  test/skill-e2e-diagram.test.ts
[test:paid] collector final outcomes: 121 executed, 0 reused; 121 passed, 0 failed, 0 manual accepted (unscored; no score-cache credit) (121 attempt records from 70 collectors; every record counts)
[test:paid] deselected testcases, zero credit (307):
[test:paid]    293  sibling case of a case shard (its own shard runs it)
[test:paid]      8  periodic-tier case (this lane runs gate)
[test:paid]      6  Bun hook placeholder of a skipped describe block (not a test)
[test:paid] VERDICT GREEN — lane gate/pr, attempt 1
  rule 108/108 · behavior 0/0 · judge 24/24 · quarantined 0 (0 failing the lane)
  SKIPPED 0 · INFRA 0 · INCOMPLETE 0 · unattributed 0 · ACTION REQUIRED 0
  wall 9m47s · cost $92.99 known + 54 of 132 trial(s) cost unknown
[test:paid] report: every planned shard accounted and passed

Sliced lane: planner → duration-packed executors → fail-closed report. Behavior cases run a pre-registered 3-trial panel (PASS at 2/3 with no contract violation); a PASS 2/3 is shown with its failed trial, never as a clean pass. Reused results retain their original provenance and expiry.

garrytan and others added 7 commits October 16, 2026 23:40
…p the lease), ps identity is timezone-independent, and reclaim never deletes a concurrent renewal

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…m rereads the record judged stale, so a renewal is never deleted or hidden

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
… holder is dead or its pid reused, unlock removes only its own lock, touch exits 3 under a live holder

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ore removing it; gstack-skill-start reports a lease the held lock kept out

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
garrytan and others added 5 commits October 17, 2026 00:57
… product name, not the English idiom (paid run 38988927989 tpa-absent-linux false positive)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…EVISION to this release

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@github-actions github-actions Bot changed the title fix(capy): trailers say behind=<n> with a fix, a skipped upgrade is never ok, and a reused pid no longer holds the upgrade lease v1.91.113.0 fix(capy): trailers say behind=<n> with a fix, a skipped upgrade is never ok, and a reused pid no longer holds the upgrade lease Oct 17, 2026
@capy-ai
capy-ai Bot merged commit 73f1394 into main Oct 17, 2026
69 checks passed
garrytan added a commit that referenced this pull request Oct 17, 2026
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant