fix(eng): slide the ship/land sentinel arm window so long runs stay cleared - #62
Merged
Merged
Conversation
…leared The ship-PR gate became unpassable from within Claude for any /ship whose invocation-to-`gh pr create` span exceeded the sentinel TTL (1200s). The sentinel minter's arm marker was written ONCE at /ship invocation and never refreshed, so ARM_TTL later the per-Bash re-mint went inert, the clearance sentinel froze at its last mint, and by create time it was long expired (observed 2026-07-21 in mutwo-skills: minted 11:44, blocked 15:10 with [sentinel: expired]). Fix: every armed Bash command that mints into the ~/dev target repo now SLIDES the session arm window forward. An actively-working /ship keeps itself armed, so the per-Bash re-mint keeps the sentinel fresh for the whole run. The 1200s TTL becomes a max-IDLE-gap rather than a whole-run budget, keeping the tight accident-guard while spanning arbitrarily long active runs. The accident-guard is intact: the slide only EXTENDS an already-armed session (guarded by session_armed_fresh) and only on a real target-repo mint, so it never arms an unarmed session nor resurrects an expired window. A bare `cd ~/dev/repo && gh pr create` with no prior /ship still blocks. Applied symmetrically to land-deploy-sentinel.sh (identical latent flaw on the merge path). Adds bats coverage for the slide, the incident repro, and the no-self-arm backstop in both suites. Bumps eng 2.8.0 -> 2.8.1 so the install cache refreshes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe land-deploy and ship-gate sentinels now use sliding idle windows. Successful Bash minting in an in-scope ChangesSentinel sliding-window behavior
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Bash as Bash PreToolUse
participant Mint as mint or mint_for_dir
participant Arm as arm_session
Bash->>Mint: Mint for WORKDIR
Mint-->>Bash: Success only for in-scope ~/dev repo
Bash->>Arm: Refresh arm marker after success
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
The ship-PR gate became unpassable from within Claude for any
/shipwhose invocation-to-gh pr createspan exceeded the sentinel TTL (1200s / 20 min).Root cause.
ship-gate-sentinel.shmints a short-TTL "clearance" sentinel that the gate (ship-pr-gate.sh) reads to allowgh pr create. The per-Bash re-mint that keeps that sentinel fresh only fires while the session is "armed", and the arm marker was written once at/shipinvocation and never refreshed. SoARM_TTL(1200s) after invocation the Bash-mint went inert, the sentinel froze at its last mint, and by create time it was long expired.Observed live in
~/dev/tooling/mutwo-skillson 2026-07-21: sentinel minted 11:44,gh pr createblocked at 15:10 (~3.4h later) with[sentinel: expired](twice). The blocking side was healthy; the regression was entirely on the mint/refresh side. Any real/ship(build + review + CodeRabbit + a QA-plan detour) overruns 20 minutes, so the gate could never clear from inside Claude.The fix: slide the arm window
Every armed Bash command that mints into the
~/devtarget repo now re-arms the session (arm_session), sliding the freshness window forward. An actively-working/shipkeeps itself armed, so the per-Bash re-mint keeps the sentinel fresh for the whole run. The 1200s TTL stops being a whole-run budget and becomes a max idle-gap: clearance stays valid as long as/shiptouched a repo within the last 20 minutes, and still dies 20 minutes after activity stops.This deliberately keeps the TTL small rather than inflating it. A big fixed TTL would just make clearance linger longer after a ship ends (a 3h TTL = 3h of stale clearance); sliding gives an arbitrarily long active run without that cost. The tradeoff (a session that keeps touching repos after a ship stays armed) is bounded by activity, backstopped by the completion-evidence layer (require mode still demands the review stamp / changelog / version bump / base-merged footprints on any create), and consistent with the existing "accident-guard, not a sandbox" framing.
Accident-guard is intact. The slide only extends an already-armed session: it is reachable only past
session_armed_fresh(so an unarmed session, i.e. no prior/ship, never reaches it) and only on a real target-repo mint (mint_for_dirnow returns non-zero out of scope). It therefore never arms an unarmed session and never resurrects an expired window. A barecd ~/dev/repo && gh pr createwith no prior/shipstill blocks.Scope: both sentinels
land-deploy-sentinel.shhad the identical latent flaw on the merge path (arm marker written once, never slid;ARM_TTL=1800). Fixed symmetrically in the same PR.Tests
New bats coverage in both suites (run individually; the full suite runs in CI per this machine's constraint):
gh pr createis allowedbats eng/hooks/tests/ship-pr-gate.bats→ 52 pass ·bats eng/hooks/tests/land-deploy-sentinel.bats→ 18 pass.Runtime refresh (important)
Editing
eng/hooks/**requires bumping the eng plugin version so the versioned install cache refreshes; bumped eng 2.8.0 → 2.8.1. The active copy Claude runs is~/.claude/plugins/cache/gstack-extensions/eng/<version>/;bin/installwas run and Production QA verified the installed 2.8.1 copies of both sentinels re-mint AND slide the arm marker live. Other machines/clones needclaude plugin marketplace update+bin/install(or the upgrade script) to pick it up.Review
/eng:crat Major tier (access-control + shared-config change): full toolkit lens set + Codex cross-model pass. Codex + code-reviewer + silent-failure-hunter: no blockers (the slide can only ever under-authorize, never over-authorize, since the gate reads the sentinel not the arm marker). comment-analyzer: all claims verified, no em-dash. test-coverage lens found one real gap (thereturn 0->return 1guard was unpinned); added a test in both suites that fails if the slide ever fires out of~/devscope.🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Chores
QA
📄 Plan view: https://claude.ai/code/artifact/30f89c30-df7a-4f5b-b661-25912728cfa1
QA driver: Claude, the building agent (this session) - the whole surface is hook scripts driven by synthetic payloads and bats, fully automatable here now.
Standard (all green): unit tests · lint/types · CI ·
/eng:cr🖥️ Development
On throwaway temp repos we replay the exact incident and the guard cases against the hook scripts, before this can merge.
bats ship-pr-gate.bats53 passgh pr createis allowedcd repo && gh pr createstill blocks~/devarmed command does NOT slide the window🚀 Production
After install refreshes the plugin cache, we drive the copy Claude actually runs with a spaced armed sequence to confirm the slide works in the real runtime.
bin/install: drive the installedship-gate-sentinel.shandland-deploy-sentinel.shwith an armedPreToolUse:Bashpayload against a temp~/devrepoeng/2.8.1Production artifacts:
~/.claude/plugins/cache/gstack-extensions/eng/2.8.1/hooks/scripts/{ship-gate-sentinel,land-deploy-sentinel}.shon this laptop (the installed plugin-cache copies the harness runs), exercised by feeding each a synthetic armedPreToolUse:Bashpayload and asserting both the re-mint and the arm-marker slide.Definition of Done:
/eng:crMajor tier) + CodeRabbit cleanQA posture:
QA_STATUS: dev_verified- every Dev QA box and Definition-of-Done box is checked. Production QA row was verified live on the installed 2.8.1 cache copies.