Skip to content

Claude Code CLI hook adapter (janus-hook), with docs - #4

Merged
eharris128 merged 8 commits into
mainfrom
claude-code-adapter
Aug 17, 2026
Merged

Claude Code CLI hook adapter (janus-hook), with docs#4
eharris128 merged 8 commits into
mainfrom
claude-code-adapter

Conversation

@eharris128

Copy link
Copy Markdown
Contributor

What this adds

Phase 1 of the Claude Code CLI integration designed in plans/claude-code-plugin-design.md (already on main), plus complete documentation for it.

Adapter (21f6860)

  • janus/adapters/claude_code.py — core-install adapter for the interactive CLI's PreToolUse/PostToolUse hook seams: payload normalization pinned to live CLI 2.1.233 captures (tool_response vs the documented tool_output, PostToolBatch fan-out, PostToolUseFailure), gate/policy evaluation modes, MCP name resolution with a known_servers allowlist, and taint-gate escalation via the CLI's ask decision (the live probe showed escalate is unrecognized and silently allows).
  • janus/cli/hook.py (janus-hook) — the command-hook shim: reads one payload on stdin, emits the CLI's hook JSON. Fails closed (unreadable policy, internal error, or its own --deadline all deny) because the CLI's own hook dispatch fails open on timeout. Subcommands: pre/post/session-start/session-end/doctor/backstop.
  • Gate mode abstains on tools the policy has no opinion about — and auto-promotes to strict default-deny under bypassPermissions, where abstention would be a silent allow.
  • Phase 1 is deliberately stateless (static policy per call; no taint/provenance until the phase-2 daemon) and delivery via settings hooks is not a security boundary against the agent it guards — the docs in this PR say both, prominently.

Documentation (4 commits)

  • CHANGELOG entry; README CLI subsection + TOC/features/install/module-tree updates; architecture/index/getting-started/CLAUDE.md coverage.
  • docs: migrate SDK adapter examples from taint= to session= — all examples now teach the non-deprecated session=Session(taint=tracker) form.
  • New docs/claude-code-deployment.md — the deployment ladder (settings hooks → plugin → plugin + managed settings) and tamper table promoted out of plans/, the #33824 force-enabled-plugin rationale, and a symptom-keyed troubleshooting section.
  • janus-hook flag reference in docs/adapters.md (--config sidecar format, --on-gate, --headless, --deadline were previously undocumented outside argparse).
  • examples/claude_code/policy.starter.json + cookbook README — copy-ready gate-mode policy (secrets-read/pipe-to-shell/tamper denies with allow fallbacks, plus the built-in tool enumeration bypassPermissions sessions require).

Validation

  • uv run pytest — 255 passed, 7 skipped (all offline; includes the 81 new adapter/shim tests)
  • uv run ruff check ., uv run mypy janus — clean
  • uv run mkdocs build — clean
  • Starter policy exercised end-to-end through janus-hook against live-captured payload shapes: denies fire under bypassPermissions, enumerated tools allowed there, unlisted tools abstain in supervised modes
  • Field-tested: the shim is deployed live on a user machine via ~/.claude/settings.json; the gate-promotion troubleshooting entry documents a failure mode hit in real use

🤖 Generated with Claude Code

eharris128 and others added 8 commits August 3, 2026 15:26
janus_options() / janus_hooks() / janus_pretooluse_hook() accept an optional
on_decision(runtime_tool_name, arguments, allowed, reason) callable, invoked
once per PreToolUse evaluation — passthrough tools and the fail-closed
internal-error path included. This gives downstream consumers (the `secure`
outreach project) a programmatic seam to durably audit hook-level policy
denies, which previously surfaced only via Python logging and the model-facing
deny reason. The callback is strictly observational: its exceptions are logged
and swallowed and can never change an enforcement outcome.

When a Session is wired, hook denies are also recorded as policy_deny session
notes, giving session.events symmetry with the taint gate_deny events.

Version 0.1.0 -> 0.1.1 so consumers can feature-detect on_decision from
janus.__version__.

Validation: uv run pytest (172 passed), ruff check, mypy janus, plus the
enforcement-review invariant walk.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ad fixtures

Design doc for shipping Janus as a Claude Code CLI guard (daemon-backed
hooks, gate mode, escalate on taint gates), plus the handoff prompt it
responds to, and verbatim hook payloads captured from CLI 2.1.233 in
tests/fixtures/claude_code_payloads/. The capture contradicts the hooks
docs: PostToolUse sends tool_response, not tool_output — the normalizer
design reads both keys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
janus/adapters/claude_code.py is the stateless decision core for the
interactive `claude` CLI seam: pinned payload contract (CLI 2.1.233
fixtures), gate/policy modes with auto-promotion under bypassPermissions,
taint-gate escalation via the verified "ask" decision value, and an
unknown-MCP-server sentinel standing in for strict_mcp_config.

janus-hook (janus/cli/hook.py) is the command-hook shim: owns its own
deadline so the CLI's fail-open timeout can never discard a deny,
isolates stdout to the hook protocol, and fails closed on every defect
of its own. Phase 1 is deliberately stateless — static policy only;
cross-call taint arrives with the phase-2 daemon.

Supporting: PolicyEnforcer.tool_names and TaintTracker
source_tools/gated_tools read-only properties, core-install import
hygiene tests, docs/adapters.md CLI section, design-doc reconciliation.

Validation: pytest (255 passed), ruff, mypy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The phase-1 CLI adapter (adapters/claude_code.py + janus-hook) was fully
documented in docs/adapters.md but invisible everywhere around it. Add:

- CHANGELOG entry for the adapter, shim, and its 81 offline tests
- README: CLI subsection under Framework Adapters, TOC/features/install
  notes (core install, no extra), module tree entries for claude_code.py
  and janus/cli/
- architecture.md: module tree + phase-1 statelessness known-limitation
- index.md feature list; getting-started.md quickstart for guarding an
  interactive claude, including the bypassPermissions gate-promotion and
  settings-tamper caveats
- CLAUDE.md: adapter paragraph + known-issue entry

Validated: uv run mkdocs build, uv run pytest (255 passed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The adapter deprecates the bare taint= tracker in favor of
session=Session(taint=tracker) (Session adds provenance and the audit
trail), but README, docs/adapters.md, and docs/taint.md still taught the
deprecated form. Update every example and knob description to session=,
noting once per page that taint= keeps working but is deprecated.

Validated: uv run mkdocs build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- adapters.md gains a Reference section for janus-hook: all subcommands,
  the full flag table (--config sidecar format, --on-gate, --headless,
  --deadline were previously documented nowhere outside argparse), and
  the security note on known_servers-based MCP name resolution.
- New docs/claude-code-deployment.md promotes the deployment ladder and
  tamper table out of plans/ (excluded from the sdist and docs site):
  attacker model, settings/plugin/managed tiers and what each defends
  against, the #33824 force-enabled-plugin rationale, the backstop, and
  the honest residual-risk statement. Added to mkdocs nav.

Validated: uv run mkdocs build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- examples/claude_code/policy.starter.json: copy-ready gate-mode policy —
  secrets-read / pipe-to-shell / guard-tamper denies with allow fallbacks,
  plus the built-in tool enumeration bypassPermissions sessions require
  (verified through janus-hook: denies fire, Skill allowed under bypass,
  unlisted tools abstain in supervised modes).
- examples/claude_code/README.md: the authoring patterns and gotchas —
  deny-then-allow-fallback, bypass enumeration, re.search anchoring and
  lookahead, deny-vacuous vs allow-strict condition semantics.
- claude-code-deployment.md gains a symptom-keyed Troubleshooting section
  (not-listed-under-bypass, hook never fires, deny didn't block, everything
  denied); getting-started links the starter policy and the new page.

Validated: uv run mkdocs build, uv run pytest (255 passed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
astral-sh/setup-uv publishes only full-semver tags (v8.3.2, v9.0.0,
v10.0.1 — no bare v8), so every workflow run since the v8 pin has died
in job setup with 'unable to resolve action', on main included.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@eharris128
eharris128 merged commit 2512be1 into main Aug 17, 2026
4 checks passed
@eharris128
eharris128 deleted the claude-code-adapter branch August 17, 2026 09:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant