You are working in a repo with shipgate.yaml already in place. Run a scan and fix the highest-severity unsuppressed finding.
-
Run a scan and locate the top finding.
agents-shipgate scan -c shipgate.yaml --ci-mode advisory
Read
agents-shipgate-reports/report.json. For v0.12+ reports the easy path isagent_summary.first_recommended_action.why— for mostblocked/review_requiredverdicts it names the top finding'scheck_idandtool_namedirectly. Three exceptions to expect:insufficient_evidenceverdict (v0.14+; the scan saw too many low-confidence tools or 4+ source warnings, an unproven binding graph, or an unresolved semantic/policy gap). There is no specific finding to fix. Readrelease_decision.evidence_coverage.evidence_gaps[]and work the selected row — the first that names a nonblank target or carries a publishablecommand(v0.16+), which is also the row the action'swhynames. Then route on the row's published authority fields, not on itskind:requires_human_review: trueandauthorable_by: "human"— every declaration row a person owes, and every reviewed inventory row. These are closed-world claims about what the deployed agent can do:declare_tool_inventory,provide_complete_inventory,declare_agent_root,declare_agent_bindings,provide_complete_binding_graph,resolve_binding_conflict,declare_action_effect,declare_action_authority, and the policy-evidence rows. Open thepath, surfaceaccepted_valuesand any scaffold to a human, and never write the content yourself — a tool inventory asserts the agent's catalog just as an authority declaration asserts its permissions. Where such a row carries acommand, that command is the rerun after a human has supplied the declaration, not a command that produces it; running it before then just repeats the same gap.- A
commandon such a row is not an exception to that. Every evidence-gap row keepsrequires_human_review: trueandauto_apply: false— including theprovide_sourcerow that regenerates a stale--diff-fromcomparison base — and for a"human"-tagged rowverifyemitsfix_task.actor = "human"withsafe_to_attempt: false. The command tells the human (or an agent the fix task has separately authorized) exactly what to run; it does not make the row agent-owned. Do not infer authority fromkindor from the shape of apath— read the authority fields, and if they say human, surface the command rather than running it. - Where agent authority does come from.
verify'sfix_taskis the only thing that grants it: act mechanically only whenfix_task.actor == "coding_agent"andfix_task.safe_to_attemptis true, and then only withinallowed_repairs[]. - The one row that reaches that state (contract v26 / report v0.42). A row the scan could answer from its own evidence carries
next_action.authorable_by: "coding_agent"withsuggested_patch_kind: "declare_action", andverifyroutes it ascontrol.next_action.kind: "confirm_declarations"— an exactapply-patchescommand plus the list of questions, each tagged. Run that command, then re-run verification before committing or pushing: the command editsshipgate.yaml, soagent controlrefuses withworkspace_changedthe moment it succeeds and the permissions printed beside the route no longer authorize anything. The re-run is a fresh decision, and a declaration that makes a risk judgeable is exactly what can move the verdict to one only a person may clear — act on what the re-run authorizes, not on what this one did. A human reviews the manifest change at the PR, because writing the manifest touches the trust root. Everything in the bullets above still holds for every other row: a question tagged"human", any authority oragent_bindingsblock, anoverride, and adeclaration_driftrow asking someone to re-confirm an answer are still yours to surface, never to write. Do not fill a blank the scan left, do not weaken a declaration the manifest carries, and do not reconstruct the edit by hand when the route is absent — its absence is the answer. Where the headline has room for it,control.reasonalso names the cause; it shares a 400-byte budget and is dropped whole, so read it if it is there and never branch on its presence. - When no row names a target and none carries a command, there is nothing to open or run, and the
whyfalls back to "gather deeper sources" (MCP/OpenAPI inputs, eval traces, additional source files) — that wording now means it truthfully.
- Evidence-coverage-driven
review_required(sub-threshold low-confidence/static evidence; no specific finding to fix). The action'swhydescribes the evidence situation — there is nocheck_idto parse out. If you see "low-confidence evidence" or "static-only" in the why-text, follow that guidance instead of looking for a top finding. On this verdictrelease_decision.reasonnever names a gap, and the action names one only on the evidence-first branches: with auto-applicable patches and sub-threshold evidence you get theapply-patchescommand instead, with the evidence gap noted in thewhy. Readevidence_gaps[]directly rather than inferring it from either field. auto_appliable_patches > 0. The action proposesapply-patches; the why-text names the apply-patches command, not a specific finding. Walkfindings[]for the actual top entry.
Fall back to picking the entry with the highest severity (
critical > high > medium > low > info) and"suppressed": falsewhenever the action doesn't name a finding directly. -
Look up the check definition.
agents-shipgate explain <CHECK_ID> --json
This returns the
CheckMetadatawithdescription,rationale,fires_when,evidence_fields,recommendation. -
Diagnose the fix. There are exactly four legitimate responses to a finding. v0.12+ reports project the routing via
agent_action:Response When agent_action(v0.12+)Add the missing policy / scope / annotation to shipgate.yamlThe check is correct; the manifest just hadn't declared the safeguard yet propose_patch_for_review(aset_pointer/append_pointerpatch is attached) orescalate_to_human(no patch — you write the entry by hand)Override the heuristic via risk_overrides.tools.{tool}.{tags,remove_tags}The risk classification is wrong (e.g. a GET endpoint that picked up the destructivetag because of a misleading operationId)escalate_to_humanSuppress the finding via checks.ignorewith areasonThe check is correct but you've decided to accept the risk explicitly (e.g. "tool deprecated 2026-Q2") escalate_to_human(the futuresuppress_with_reasonvalue is reserved for checks that pre-classify themselves as suppressible)Fix the underlying tool definition The tool spec itself is wrong (missing description, broad scope, free-form action field) escalate_to_human -
Apply the fix. Edit either
shipgate.yamlor the tool source file. Do not delete tools wholesale to silence findings. -
Re-scan and confirm the count went down.
agents-shipgate scan -c shipgate.yaml --ci-mode advisory
The previously-failing fingerprint should be gone from
report.json. -
Report back:
- What was the original finding (check ID, tool, severity)
- Which of the four response types you used
- The diff to
shipgate.yaml(or other file) you applied - The new finding count
| Check | Typical fix |
|---|---|
SHIP-POLICY-APPROVAL-MISSING |
Add the tool to policies.require_approval_for_tools with a reason |
SHIP-POLICY-CONFIRMATION-MISSING |
Add the tool to policies.require_confirmation_for_tools |
SHIP-SIDEFX-IDEMPOTENCY-MISSING |
Add an idempotency_key parameter, set idempotentHint: true annotation, or list under policies.require_idempotency_for_tools |
SHIP-AUTH-MISSING-SCOPE |
Declare the scope on the tool (in OpenAPI security or MCP metadata) and in permissions.scopes |
SHIP-AUTH-MANIFEST-BROAD-SCOPE |
Replace * / admin with the specific operation scope(s) |
SHIP-DOC-MISSING-DESCRIPTION |
Add a 20+ char description to the tool definition |
SHIP-SCHEMA-BROAD-FREE-TEXT |
Constrain the parameter with an enum, structured schema, or narrower fields |
SHIP-SCHEMA-MISSING-BOUNDS |
Add maximum to the numeric parameter |
SHIP-INVENTORY-LOW-CONFIDENCE-PRODUCTION-SURFACE |
Declare the tools through MCP/OpenAPI for higher-confidence inventory; or move target to staging |
- Do not blanket-suppress an entire check. Suppressions are per-tool unless the check is genuinely irrelevant for this repo.
- Do not write
reason: "false positive"without explanation. Reviewers should be able to read the reason and understand the decision in 60 seconds. - Do not edit
agents-shipgate-reports/. It's regenerated each run.
- The previously-failing finding's fingerprint is no longer present in
report.json - The fix is committed in a single, focused diff (manifest change + reason)
- If you used
checks.ignore, thereasonis concrete (a date, a ticket link, or "tool deprecated; see roadmap")