Repository navigation
Description
Problem
CLAUDE.md:32, skills/agents-shipgate/SKILL.md:112 and AGENTS.md:403 promise a structured next_action JSON line on stderr in agent mode, without qualification. In practice 16 of 38 error paths emit it:
- 15 CLI modules never call the emitter: fixture, skill, install_hooks, org, feedback, registry, self_check,
_register_baseline, capability, authorization, evidence_packet, main, attest, mcp and trigger (for examplecli/attest.py:122typer.echo(...); raise typer.Exit(3)); - Click usage and
BadParametererrors never emit it:diff --base nope-ref --jsonexits 2 viatyper.BadParameter(cli/diff.py:98) with no JSON line.
Raw str(ValidationError), with pydantic type codes, input values and errors.pydantic.dev URLs, becomes the user- and agent-facing message (cli/preflight.py:536 and other boundaries). The agents-shipgate-mcp console script dumps a traceback when the [mcp] extra is absent (mcp_server/server.py:362).
Evidence
#59(medium) Agent-mode next_action JSON is promised for all errors but emitted on 16 of 38 error paths- Where:
src/agents_shipgate/cli/attest.py:122 - Evidence: attest.py:122
except InputParseError as exc: typer.echo(...); raise typer.Exit(3) from exc.diff --base nope-refexits 2 via typer.BadParameter (diff.py:98) with no JSON line. - Observed: A Claude Code session (agent mode auto-enabled via CLAUDECODE=1) runs
agents-shipgate agent handoff --from agents-shipgate-reports/verifier.json --jsonbefore verify has run. Stderr is the prose lineInput parsing error: verifier.json not found: ..., exit 3, no JSON line. The agent's documented routing rule ('parse the JSON line, follow next_action') has nothing to parse; the same happens forattest,trigger --changed-files,capability export --config,baseline save -c,org bundle --from,feedback export --from,mcp audit --diff,skill lint <path>,fixture run <name>,install-hooks --target cursor. - Repro: cd $S/sra; AGENTS_SHIPGATE_AGENT_MODE=1 ./shipgate agent handoff --from /nonexistent.json --json; AGENTS_SHIPGATE_AGENT_MODE=1 ./shipgate trigger --changed-files /nonexistent.txt --json; AGENTS_SHIPGATE_AGENT_MODE=1 ./shipgate capability export --config /nonexistent.yaml --out /tmp/x.json # each: prose only, no JSON line
- Where:
#82(low) Raw pydantic validation dumps are surfaced as user-facing and agent-facing messages- Where:
src/agents_shipgate/cli/preflight.py:536 - Evidence: preflight.py:536
raise ConfigError(f'Invalid {source_label}: {exc}') from exc. - Observed:
echo '{"planned_files":["x.py"]}' | agents-shipgate preflight --plan - --jsonin agent mode:messageisInvalid Preflight plan: 1 validation error for PreflightPlanV1\nplanned_files\n Extra inputs are not permitted [type=extra_forbidden, input_value=['x.py'], input_type=list]\n For further information visit https://errors.pydantic.dev/2.13/v/extra_forbidden;next_actionroutes toverify --preview, which does not help with the plan shape. - Repro: cd $S/sra; echo '{"planned_files":["x.py"]}' | AGENTS_SHIPGATE_AGENT_MODE=1 ./shipgate preflight --workspace . --config shipgate.yaml --plan - --json
- Where:
#94(low) The agents-shipgate-mcp console script dumps a traceback when the [mcp] extra is absent- Where:
src/agents_shipgate/mcp_server/server.py:362 - Evidence: server.py:362
def main() -> None: serve_stdio(). - Observed: A user adds
agents-shipgate-mcpto their MCP client config afterpipx install agents-shipgate. The client shows a crashed server with a traceback, and the suggestedpip installdoes not reach the pipx venv.
- Where:
Why it matters
- Coding agents: the documented routing rule (parse the JSON line, follow
next_action) has nothing to parse on more than half of the error paths. - Local CLI user: pydantic dumps and tracebacks instead of one actionable line.
Proposed fix
- One exception and
UsageErrorseam at the root group (a wrapper aroundtyper.main.get_command(app), or overridingmain()/Context.fail) that mapsConfigError,InputParseError,AgentsShipgateError,ValidationErrorand Click usage errors to the exit table, prints the prose line and callsemit_agent_mode_error_action. Delete per-command echo-and-exit pairs the seam covers. - One
ValidationErrorformatter (location, reason, accepted keys) used at every boundary. mcp_server/server.pymain(): catchConfigError, print one line with an install hint matching the install mode, exit 2.- If any path stays uncovered, qualify the promise in CLAUDE.md, AGENTS.md and SKILL.md (protected paths). CHANGELOG.
Protected paths: yes; this change touches protected paths and needs owner review before merge.
Acceptance criteria
- A test walks every leaf command in the Click tree, triggers an invalid input in agent mode, and asserts exactly one parseable
next_actionJSON line on stderr and a documented exit code. -
diff --base nope-ref --jsonin agent mode emits the JSON line. - No error message contains
errors.pydantic.devortype=codes (test over representativeValidationErrors). -
agents-shipgate-mcpwithout the extra exits 2 with one line and no traceback.
Out of scope
- Exit-code table normalisation: contract: one exit-code table and one command table, rendered and tested against CLI #1045.
- Classifying failures as ours or theirs: Distinguish product defects from user configuration errors in adopter-facing output #328.
Source
Engineering review 2026-10-12, findings #59, #82, #94 (the maintainer's private copy of the 2026-10-12 engineering review § Findings; verifier reproductions in its evidence companion, claims A47, A57, A56, A104).
Dimensions: cli-ux-agent-ergonomics, packaging-deps.
Implement after: #1022.
Related: #328 (ours-versus-theirs error classification).
Activity
docs(agents): name the schemas check emits and qualify the agent-mode…