Make ship and ship-autonomous pre-flight run every guard before reporting, add worktree env-parity and browser-availability checks, give each pre-flight prompt a named headless default, and add an external-blocker class to CI triage so an expired token or billing block is never autofixed as a code defect.
Ship pre-flight stops at the first failing guard, so a session with three blockers costs three full spin-ups — and it never checks the one that has caused two phantom bugs, a linked worktree missing its .env. This makes pre-flight report every blocker at once, adds the env-parity and browser-availability checks, names a headless default for each prompt, and teaches CI triage that a billing block is not a code defect.
Read and implement all steps in the plan at docs/plans/harden-ship-preflight.md — Harden ship pre-flight against environment blockers. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/harden-ship-preflight.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
More ways to run this plan — goal & workflow prompts, file path
Achieve this goal: Harden ship pre-flight against environment blockers. The plan at docs/plans/harden-ship-preflight.md describes one approach — use it as reference, but optimize for the outcome. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/harden-ship-preflight.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
harden-ship-preflight.html
docs/plans/harden-ship-preflight.html
docs/plans/harden-ship-preflight.md
Context
The story behind this plan — what prompted the work and why it matters now.
The 2026-08-14 usage report attributes roughly a quarter of all not_achieved
sessions to pre-flight: "Five-plus ship-autonomous invocations halted at Step 1
on failed gh auth, so nothing was committed or PR'd and the sessions were a
total loss." The report treats the halts as correct behaviour, and they are —
the guards did their job. What is wrong is the cost of learning about them.
Pre-flight stops at the first failure. skills/ship/SKILL.md Step 1 says
so explicitly, and the ship-autonomous reference is written the same way:
each check STOPs on its own. A session with an unauthenticated gh, a dirty
tree, and a missing worktree .env therefore costs three separate spin-ups —
fix one, re-invoke, discover the next. Running all guards and reporting one
table with every blocker and its remediation collapses that to one, and
changes nothing about the halt itself.
Automatic re-auth is deliberately not added. The report suggests teaching
the skill to attempt re-auth rather than report. The standing rule in this
user's CLAUDE.md is the opposite — "Report blockers verbatim and stop; do
not attempt workarounds or guess at a re-auth" — and re-auth is an interactive
browser flow that cannot succeed unattended anyway. The blocker stays a
blocker; only the round-trip count changes.
A linked worktree missing its .env has caused two phantom bugs. The
report records a Clerk sign-in regression root-caused to a missing worktree.env rather than the CSS change that appeared to cause it, and notes the same
culprit behind an earlier phantom "missing nav button" bug. Gitignored env
files do not travel with git worktree add, so every worktree starts without
them and the failure presents as a code defect in whatever was edited last.
Detection is two commands; the file is never copied automatically, because it
holds secrets and a silent copy is the wrong default even when copying is the
right action.
Browser verification degrades silently. Step 2.5's browser block has no
availability probe, so when the MCP is absent in a headless or non-interactive
session the step is simply skipped — and the commit and PR body still read as
though the change was verified. The report names this directly: "Browser MCP
were unavailable in non-interactive sessions, so ... visual verification
[was] silently downgraded to documented assumptions." wcag-compliance-reviewer
already settled the convention in 1.5.2 — write UNVERIFIED — no browser
rather than omit the claim — and this adopts it.
The marker goes in the PR body, not the commit body. An earlier draft of
this plan required both, which is not reachable from the files it changes:ship-autonomous delegates commits to commit-agent, whose Step 4 is a
single--m git commit -m "<message>" against a message capped at 72
characters — there is no commit body to write into. Giving commit-agent one
is a behaviour change to a skill with several callers and belongs to its own
plan. The PR body is also where the claim actually misleads someone: a
reviewer reads it, a commit trailer they do not. So the requirement is the
session output plus one line in pr-agent's Test Plan template, andpr-agent joins the file list to make that reachable.
Pre-flight prompts have no headless default. The uncommitted-plan-files
gate calls AskUserQuestion with three options and no stated fallback, so
under claude -p it stalls or improvises. plan-agent already solved this inbuild: "Headless, take each gate's named default and log it." The merge
gate needs no change — agents/agent-merge.md already states that Step 3'sAskUserQuestion does not apply and that anything not green is reported
instead.
CI triage has no non-defect class. references/ci-autofix.md classifieslint, typecheck, peer-deps, and ask the user. Nothing represents a
failure that is not the code's fault, so an expired CLAUDE_CODE_OAUTH_TOKEN
or a billing block burns autofix attempts against correct code. The report
names the token as recurring friction, and the same rule is already written
into plan-agent's red-green-verify guidance: "A failing GitHub Actions check
is not a code defect until proven one." git-agent is the plugin that acts on
CI failures and is the one place it is missing.
Scope. Reference-file and skill-body edits plus a content test. No hooks,
no scripts, no behaviour that runs outside a skill invocation.
Files that change
Every file this plan touches, and what happens to each one.
kit/plugins/git-agent/skills/ship-autonomous/references/preflight-and-verify.mdmodified run-all-then-report, env parity, browser probe, headless defaultskit/plugins/git-agent/skills/ship/SKILL.mdmodified same run-all-then-report contract and env-parity check in its own Step 1kit/plugins/git-agent/skills/ship/references/preflight-guards.mdnew ship's own copy of the table format and guard commands; the core carries the guard statements but is capped at 600 words by tests/plugins/test-skill-split-git-social.sh, and a skill can only bundle files under its own directorykit/plugins/git-agent/skills/pr-agent/SKILL.mdmodified Step 5's body template carries theUNVERIFIED — no browserline when the caller reports itkit/plugins/git-agent/skills/ship-autonomous/references/ci-autofix.mdmodifiedexternal-blockerclass, placed first in the table- kit/plugins/git-agent/
README.mdmodified document the pre-flight table, the env check, and the external-blocker classCHANGELOG.mdmodified 4.17.0 entry
.claude-plugin/marketplace.jsonmodified git-agent 4.16.1 to 4.17.0tests/plugins/test-ship-preflight.shnew content assertions over both pre-flight surfaces and the CI table
Steps
The step-by-step work, in order — each step says what to do, why it matters, and how to check it worked.
references/preflight-and-verify.md Step 1 so every guard runs before anything is reported: clean tree, uncommitted plan files, detached HEAD, gh auth status, and the two new checks below, collected into one PASS/FAIL/BLOCKED table with a verbatim remediation command per failing row. Keep every existing halt condition — the skill still stops on any BLOCKED row, it just stops knowing all of them.
gh and a dirty tree produces one table naming both, and the skill mutates nothing.git rev-parse --git-dir differs from git rev-parse --git-common-dir, then compare the .env* files present in the main checkout against those in this worktree and report any that are missing, with the exact cp command per file. Never copy.
.env the worktree lacks, the row reads BLOCKED and quotes the cp command; in a non-worktree checkout the row is absent entirely.preview_start is reachable. When it is not, skip the browser steps, state UNVERIFIED — no browser in the session output, and carry that string forward as the verification result the PR body must report.
wcag-compliance-reviewer 1.5.2.UNVERIFIED — no browser and continues to commit; with it available, the phrase is absent and the preview checks run.skills/pr-agent/SKILL.md Step 5's body template: when the invoking skill reports a verification marker, the Test Plan section carries it verbatim; when it reports none, the section is unchanged.
pr-agent is what writes the PR body, so without this line the marker never reaches the surface where a reviewer would see it, and the acceptance criterion would be unmeetable from inside this plan's scope.ship-autonomous run with no browser produces a PR whose Test Plan section contains UNVERIFIED — no browser; a pr-agent invocation with no marker reported produces the existing template unchanged.AskUserQuestion a named headless default and say so in one line, matching plan-agent build's wording — the uncommitted-plan-files gate defaults to abort.
claude -p the tool is unavailable, and an unstated fallback means the skill improvises at exactly the gate that exists to stop it.AskUserQuestion it raises, and the plan-files gate's default is abort.skills/ship/SKILL.md Step 1, which carries its own copy of the guards rather than sharing the reference file.
ship is the entry point used when the user does not want CI watching, so leaving it on first-failure semantics means the fix only lands for half the callers.external-blocker row to the references/ci-autofix.md classification table, ordered above the autofixable classes: signatures billing, quota, spending limit, Bad credentials, refusing to allow, token-expiry text, or every job failing with no test output. Its action is to report the failure verbatim as an external blocker, with no autofix and no increment of the three-attempt cap.
plan-agent's planning guidance while the plugin that acts on CI lacks it.external-blocker, and the file states that the attempt cap does not advance for it.gh run view <id> --json jobs and treat all-jobs-failed with sub-minute durations and empty --log-failed output as external-blocker.
gh run view --json jobs command and states the all-failed-fast-and-empty condition.tests/plugins/test-ship-preflight.sh asserting: both pre-flight surfaces describe a single combined report, both carry the env-parity check gated on --git-common-dir, the browser probe names UNVERIFIED — no browser, pr-agent's Step 5 body template carries the marker line, every AskUserQuestion in the pre-flight path has a named headless default, and ci-autofix.md lists external-blocker ahead of lint.
pr-agent assertion is specifically what stops the marker requirement from passing its test while never reaching a PR — the failure mode this plan's earlier draft had.bash tests/plugins/test-ship-preflight.sh reports zero failures, and removing any one clause from any of the four files turns exactly one check red..claude-plugin/marketplace.json, add the CHANGELOG entry, and document the pre-flight table, the env check, and the external-blocker class in the README.
git fetch origin && BASE_REF=main node scripts/check-plugin-versions.mjs exits 0.Tests
The tests that prove the change does what it promises.
--git-common-dir-gated env check, the browser probe names UNVERIFIED — no browser and pr-agent's body template carries it through to the PR, each pre-flight AskUserQuestion names a headless default, and ci-autofix.md lists external-blocker above lint and states the attempt cap does not advance for it; Run: bash tests/plugins/test-ship-preflight.shDefinition of done
The plan counts as done when every statement below is true — check each one off as you verify it.
Final check
One last pass to confirm the whole change works end to end.
Run bash tests/plugins/test-ship-preflight.sh and confirm zero failures, thengit fetch origin && BASE_REF=main node scripts/check-plugin-versions.mjs and
confirm exit 0. Run the full plugin suite and confirm no other git-agent test
regressed.
End-to-end, create a scratch repo outside this repository with a committed.gitignore covering .env, a .env file at the main checkout, and a linked
worktree added via git worktree add. From the worktree, with an uncommitted
change present, invoke ship and confirm the run produces one report naming
the missing .env with its cp command, halts, and leaves the tree untouched
— then confirm that fixing only the env and re-invoking surfaces any remaining
blocker rather than a fresh single-blocker stop.
For the CI class, take a real failed run from a billing-blocked workflow —gh run list --json databaseId,conclusion on a repo where one exists — and
confirm gh run view <id> --log-failed returns no test output while--json jobs shows every job failed within seconds, which is the condition
Step 7 keys on. Record the observed durations in the CHANGELOG entry so the
threshold is measured rather than assumed.
Wrapping up
Three gates that must all pass before this plan is marked completed.
Completion Report
- Acceptance criterion 4, first clause ("a
ship-autonomousrun with no browser produces a PR whose Test Plan section contains `UNVERIFIED - no browser
") — **verified as a text contract at its point of use, not by an executed no-browser run.** A browser MCP is present in the implementing session, so the no-browser branch cannot be induced here.test-ship-preflight.shcheck 5 asserts the marker rule sits insidepr-agentStep 5's Test Plan section — the blockgh pr create` writes into — and a mutation run confirms deleting the rule turns exactly that check red. The clause's second half (no marker reported → template unchanged) was exercised for real by this plan's own PR, which reports no marker and carries the unmodified template. - End-to-end scratch-repo rehearsal from the Verification section (a linked worktree missing its
.env, invokingship) - not executed. Driving a
disable-model-invocationskill end to end requires a separate interactive session; the guard's logic is asserted by checks 1–3 instead. - CI-class thresholds
- measured, and the plan's assumption was corrected. The plan proposed keying
external-blockeron "all jobs failed with sub-minute durations and empty--log-failedoutput". Measurement onshawn-sandy/agentics(2026-08-14, last 300 runs) shows the eight genuine failures also completed in 6–22 s, so duration does not discriminate on this repo. The shipped rule makes the empty log load-bearing and demotes duration to corroboration. Numbers recorded in the CHANGELOG entry. - Two suite failures are pre-existing, not regressions
test-plan-phases.mjs(plan-agent's unphased-render baseline hash) andtest-dist-transforms.mjs("No dist/ directory"). Both reproduce identically on a cleanorigin/maintree materialized viagit archive, and neither reads a file this branch touches. 60 of 62 suite scripts pass; no git-agent test regressed.test-skill-behavior-baselines.shwas skipped (it shells out to theclaudeCLI per recorded fact, as CI also skips it) — none of the five skills it baselines is modified here.kit/plugins/git-agent/skills/ship/references/preflight-guards.md, a file the plan did not list- added because
ship/SKILL.mdis capped at 600 words bytests/plugins/test-skill-split-git-social.shand the full contract did not fit. The core keeps the guard statements, the reference carries the commands. Recorded in the Files section.