Claude Code CLI hook adapter (janus-hook), with docs - #4
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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'sPreToolUse/PostToolUsehook seams: payload normalization pinned to live CLI 2.1.233 captures (tool_responsevs the documentedtool_output,PostToolBatchfan-out,PostToolUseFailure), gate/policy evaluation modes, MCP name resolution with aknown_serversallowlist, and taint-gate escalation via the CLI'saskdecision (the live probe showedescalateis 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--deadlineall deny) because the CLI's own hook dispatch fails open on timeout. Subcommands:pre/post/session-start/session-end/doctor/backstop.bypassPermissions, where abstention would be a silent allow.Documentation (4 commits)
docs: migrate SDK adapter examples from taint= to session=— all examples now teach the non-deprecatedsession=Session(taint=tracker)form.docs/claude-code-deployment.md— the deployment ladder (settings hooks → plugin → plugin + managed settings) and tamper table promoted out ofplans/, the #33824 force-enabled-plugin rationale, and a symptom-keyed troubleshooting section.janus-hookflag reference indocs/adapters.md(--configsidecar format,--on-gate,--headless,--deadlinewere 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 enumerationbypassPermissionssessions 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— cleanuv run mkdocs build— cleanjanus-hookagainst live-captured payload shapes: denies fire underbypassPermissions, enumerated tools allowed there, unlisted tools abstain in supervised modes~/.claude/settings.json; the gate-promotion troubleshooting entry documents a failure mode hit in real use🤖 Generated with Claude Code