Skip to main content

ctx harness

Package ctx as an AI coding harness integration (Claude Code).

Synopsis​

ctx harness init [--target claude|codex] [--mode local|plugin] [--force] [--json]
ctx harness compat --require <SEMVER>
ctx harness doctor [--json]

Description​

The harness command wires the ctx quality suite into an AI coding harness. Supported targets are Claude Code (--target claude, the default) and Codex (--target codex); unknown targets are rejected with exit code 2 and a list of supported values.

Codex​

ctx harness init --target codex generates .codex/hooks.json, hook scripts under .codex/hooks/ctx/, a starter .ctx/rules.toml, and an AGENTS.md guidance block. Review and trust the generated hook definitions with /hooks. Plugin mode generates a .codex-plugin bundle, ctx skill, lifecycle hooks, local marketplace, and optional MCP wiring.

Everything init writes comes from templates embedded in the ctx binary, and every generated file carries a header:

# generated by ctx v0.3.0 — regenerate with 'ctx harness init'
# ctx:checksum sha256:<hex>

The checksum makes regeneration safe: on re-run, files that still match their recorded checksum are regenerated in place, while files you have modified (or files ctx never generated) are skipped with a warning. --force overwrites them. JSON files cannot carry comments, so they are tracked in a manifest, .ctx/harness.lock, instead.

Two files get special treatment:

  • .ctx/rules.toml is never overwritten once it exists, not even with --force — it encodes your policy, not ctx's.
  • .claude/settings.json is never edited by ctx at all; init prints a snippet for you to merge manually.

init --mode local (default)​

Wires the current project's .claude/ directory:

FilePurpose
.claude/hooks/ctx/session-start.shSessionStart hook: prints ctx map --budget 2000 for the model
.claude/hooks/ctx/post-tool-use.shPostToolUse (Edit|Write) hook: ctx index, then ctx check --against HEAD --json
.claude/hooks/ctx/stop.shStop hook: ctx score --against <default branch> scorecard (non-blocking by default; see Blocking gates)
.ctx/rules.tomlCommented starter rules file (only when absent)
.ctx/harness.lockManifest of generated files and their checksums

It then prints two blocks to stdout (instructions go to stderr):

  1. A settings snippet to merge into .claude/settings.json — hook wiring plus a permissions block that allows Bash(ctx *) and denies Bash(ctx self-update*), Edit(.ctx/rules.toml), Edit(.claude/hooks/ctx/**), and Edit(.claude/settings.json).
  2. A guidance block for your CLAUDE.md so the model knows to use ctx.

init --mode plugin​

Scaffolds a distributable Claude Code plugin in the current directory:

FilePurpose
.claude-plugin/plugin.jsonPlugin manifest (version = the generating ctx version)
.claude-plugin/marketplace.jsonLocal marketplace (ctx-local) for manual installs
hooks/hooks.jsonHook wiring via ${CLAUDE_PLUGIN_ROOT}
hooks/*.shThe same three hook scripts as local mode
settings.jsonThe permissions block (allow/deny above)
skills/ctx/SKILL.mdSkill teaching the model the ctx workflow
README.mdManual install walkthrough (/plugin marketplace add ./, /plugin install ctx@ctx-local)
.mcp.jsonMCP server wiring (ctx serve --mcp) — only when this binary was built with the mcp feature; otherwise a stderr note explains how to enable it

Hook behavior (fail open, loudly)​

Every generated hook script starts with a compatibility guard:

ctx harness compat --require "<version baked at generation>"

If the installed binary is older than the templates (exit code 3), the script prints a warning to stderr and exits 0 without performing its action — the session is never blocked, but you are told why. Model-bound content (map, check JSON, scorecard) goes to stdout; human-facing notices always go to stderr.

Blocking gates (CTX_GATE_BLOCKING)​

The generated Stop hook runs ctx score --against <default branch> --fail-on "check_violations>0,new_duplication>0". By default a failed gate only prints the scorecard and a stderr note — the session stops normally. Set CTX_GATE_BLOCKING=1 (exactly 1) in the environment Claude Code runs in to turn gate failures into a blocking stop: the hook exits 2, which Claude Code treats as "keep working", so the session continues until the failed conditions in the scorecard are addressed.

Only a genuine gate failure ever blocks. Everything else fails open:

ctx score resultCTX_GATE_BLOCKINGHook exitEffect
Gates pass (exit 0)any0Session stops normally
Gate condition fired (exit 1)unset / anything but 10Non-blocking; a stderr note points at the scorecard
Gate condition fired (exit 1)12Blocking stop; Claude keeps working on the findings
Operational error (exit 2, compat mismatch, ctx not on PATH)any0Fail open with a stderr warning

Pair it with CTX_GATE_LOG (consumed by ctx score itself, not the hook) to record every gate evaluation — including whether blocking mode was on — in a local JSONL log; see ctx score — Gate logging.

Upgrading: hook scripts are generated files. If your .claude/hooks/ctx/stop.sh predates CTX_GATE_BLOCKING, re-run ctx harness init after updating ctx to regenerate it (ctx harness doctor's templates_stale check tells you when this is due).

compat --require <SEMVER>​

Exits 0 when the running binary satisfies the requirement, otherwise prints exactly one line to stderr and exits 3 (reserved exclusively for this subcommand).

A bare version (0.2, 0.2.1) is a floor: the binary must be at least that version. An expression with operators (^0.2, >=0.2, <0.4, 0.2.*) is parsed as a full semver requirement. Unparseable input is an operational error (exit 2), never 3.

doctor​

Runs independent checks and reports findings (error, warning, info):

CodeChecks
binary_versionctx version and whether the mcp feature is compiled in (info)
harness_not_initializednothing scaffolded yet (info)
templates_stalegenerated files come from a different ctx version
index_missing / index_schema / index_staleindex existence, schema version, freshness vs file mtimes
rules_missing (info) / rules_invalid.ctx/rules.toml presence and validity
hooks_missing / hooks_modifiedgenerated hook scripts exist and match their checksums
settings_not_wired.claude/settings.json does not reference .claude/hooks/ctx/
mcp_unavailable / mcp_not_wired.mcp.json vs the binary's mcp feature

Checks do not cascade: a missing index and an invalid rules file are both reported in the same run.

Exit Codes​

CodeMeaning
0Success (init), requirement satisfied (compat), healthy (doctor)
1doctor found problems (errors or warnings)
2Operational error (unknown --target, bad arguments, IO failure)
3Version requirement not met — reserved exclusively for harness compat

Examples​

Wire ctx into Claude Code​

ctx harness init
# merge the printed snippet into .claude/settings.json,
# add the printed block to CLAUDE.md, then:
ctx harness doctor

Build a distributable plugin​

mkdir ctx-plugin && cd ctx-plugin
ctx harness init --mode plugin
claude
# /plugin marketplace add ./
# /plugin install ctx@ctx-local

Regenerate after updating ctx​

ctx harness doctor          # templates_stale tells you regeneration is due
ctx harness init            # regenerates unmodified files, skips your edits
ctx harness init --force    # overwrites your edits too (rules.toml excepted)

Machine-readable output​

ctx harness init --json     # file actions + settings snippet in the envelope
ctx harness doctor --json   # health report (see docs/json-output.md)

See Also​