Skip to content

hook

ward hook contains the adapters that generated agent integrations call. You normally do not run it yourself.

Terminal window
ward hook claude-code [-C <dir>] [--learn-after <n>] [--exclude-spec] [--event-stdin]
ward hook claude-code [-C <dir>] --guard-approved-specs --event-stdin
ward hook native --agent <agent> [-C <dir>] [--event-stdin] [--guard-approved-specs]
OptionEffect
-C, --directory <dir>Run for the project in <dir>. See Nested projects.
--learn-after <n>Learn recurring quality failures after n occurrences (default 3).
--exclude-specSkip the openspec and openspec-archived gates in the quality check, because spec validation runs separately.
--agent <agent>(native only) Harness whose payload and response format to use.
--event-stdinRead the hook event JSON from stdin. It is ignored when stdin is an interactive terminal.
--guard-approved-specsAct as the opt-in PreToolUse approved-artifact guard. No quality gates, learning, or spec validation run in this mode.
--verbosePrint diagnostics to stderr. See Verbose diagnostics.

ward integrate claude-code creates .claude/hooks/ward-edit-write, which runs ward hook claude-code --learn-after 3 --exclude-spec --event-stdin after Claude Code’s edit tools and when Claude stops.

Without --event-stdin, or when the payload is empty or not valid JSON, the adapter behaves as it always has. It runs the default profile gates as a PostToolUse check. When they pass, it returns any learning prompts as PostToolUse context. When they fail, or Ward cannot run, it writes the failures to stderr and exits 2, which Claude Code shows to the agent so it can fix them.

With an event payload:

  • PostToolUse: a partial check runs for the edited tool_input.file_path, then the path goes through spec validate. Spec findings are reported on stderr with exit 2, alongside any quality failures. An event without a path runs every gate in full.
  • Stop: the full default quality check and the configured spec validations run. Findings block the stop once, using decision: "block". When stop_hook_active is true, or Ward cannot run, they are shown to the user as a systemMessage. This full check also catches files changed by shell commands, which send no edit event. The exit code is 0.

See Claude Code and learning.

ward hook native accepts bounded JSON from the supported harnesses, runs quality checks and targeted OpenSpec validation, and translates guard refusals into the harness’s response shape.

  • --agent is required and must be a native harness ID: codex, cursor, copilot, gemini, windsurf, cline, factory-droid, or amp.
  • Malformed, missing, or oversized payloads (over 1 MiB) fail safely and never print payload contents.

See native harness integrations.

When -C names a project below the repository root:

  • An edit event whose paths all lie outside the project runs no gate and succeeds.
  • A Claude Code Stop event runs the full check only when a file under the project differs from HEAD in the working tree or the index.
  • Projects at the repository root behave as before.

A native hook started outside every project, as the shared repository-level hooks of monorepos are:

  • checks each configured project (a directory with ward.toml) that contains an edited file, and at turn end each configured project with changes;
  • starts findings with Ward checked <project>:;
  • has an approved-artifact guard that protects the repository root’s openspec/ directory, if there is one.

ward integrate claude-code --guard-approved-specs creates .claude/hooks/ward-openspec-guard, which runs ward hook claude-code --guard-approved-specs --event-stdin before Claude Code’s Edit, Write, MultiEdit, NotebookEdit, Bash, and Skill tool calls. The adapter reads the whole PreToolUse payload (up to 16 MiB, because a Write carries the full file) and passes the edited file_path or notebook_path, the Bash command (resolved against the payload’s cwd), or the Skill name to the same checks as spec guard.

SituationOutputExit
AllowedNothing, so Claude Code’s own permission handling still applies0
RefusedPreToolUse JSON with permissionDecision: "deny" and the reason as permissionDecisionReason0
Payload missing from a pipe, larger than 16 MiB, or not a JSON objectA deny stating that Ward could not inspect the call0
Ward cannot decide, for example outside a detectable projectThe problem on stderr; Claude Code treats it as non-blocking1
Any other event, such as StopNothing0

The guard never answers allow or ask, so it cannot bypass your permission prompts.

The inherited --verbose option also applies to nested hooks, for example ward hook claude-code --verbose. Diagnostics are written to stderr, leaving the hook JSON response on stdout unchanged. They never include findings, payload contents, or command lines.

Adapter012
hook claude-codeGates passed and no spec findings; always for Stop events—Gates failed, spec findings, or Ward could not run (reported to the agent on stderr)
hook claude-code --guard-approved-specsAllowed or refused; see the guard tableWard could not decide—
hook nativeDepends on the harness’s hook contract; see native harness integrations