Skip to content

feat(a1): Stop-hook false-completion firewall - #22

Merged
SollanSystems merged 3 commits into
mainfrom
feat/a1-stop-hook
Jul 4, 2026
Merged

SollanSystems merged 3 commits into
mainfrom
feat/a1-stop-hook

Conversation

@SollanSystems

Copy link
Copy Markdown
Owner

PR3 (A1) of the adoption-slices plan (docs/superpowers/plans/2026-07-03-adoption-slices.md).

What

  • hooks/stop_firewall.py — a Claude Code Stop hook that blocks a session stop with an actionable reason when the cwd's .loop/ contract claims Succeeded but loop doctor reports issues. Safety invariants, each pinned by tests:
    • Never locks a session: fail-open on any error (always exit 0), stop_hook_active read defensively, blocks at most once per session per doctor-issues digest (tempdir sentinel), sentinel I/O failure errs toward skip.
    • Strict no-op without .loop/ — the doctor subprocess only spawns when a contract exists and claims Succeeded.
    • Works in a dependency-free install: doctor's structural fallback still emits contradictory_terminal, so the firewall fires without jsonschema/pyyaml.
  • Manifest registration — inline top-level hooks.Stop entry in .claude-plugin/plugin.json (${CLAUDE_PLUGIN_ROOT}, timeout 90; internal subprocess cap 60 fires first), plus a registration test against the real manifest.
  • 10 subprocess acceptance tests (honest / lying / absent / in-flight / forced-error / garbage-doctor / livelock matrix) + CHANGELOG entry.

Review trail

Per-task reviews: both Approved. Whole-branch review: Ready to merge — Yes (empirically confirmed the dep-free structural-fallback path and no self-block on this repo's own dogfood .loop/); its test-hardening suggestions folded in as 215b0a1, re-review approved. Follow-up noted for maintainer: whether _cli_command() should prefer the plugin-root CLI over a PATH loop (plan currently specifies PATH-first).

Tests

Canonical suite: 244 passed / 8 skipped (baseline 234/8 at branch; +10 firewall tests). Frontmatter + self_eval gates green.

Post-merge operator step: live-smoke the hook (plugin-cache refresh + Claude Code restart, scaffold a lying terminal, end a session → block appears).

🤖 Generated with Claude Code

https://claude.ai/code/session_01As92NaD7jMDbnQTqXDzBVa

SollanSystems and others added 3 commits July 4, 2026 15:24
…out .loop

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wire hooks/stop_firewall.py into .claude-plugin/plugin.json under the
top-level hooks.Stop key (python3 ${CLAUDE_PLUGIN_ROOT}/hooks/stop_firewall.py),
so marketplace installs get the false-completion firewall with zero config.
Adds the manifest-registration acceptance test and the A1 CHANGELOG entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…o pytest tmp

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 4, 2026 19:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@SollanSystems
SollanSystems merged commit 77e7557 into main Jul 4, 2026
5 checks passed
@SollanSystems
SollanSystems deleted the feat/a1-stop-hook branch July 4, 2026 19:48

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 215b0a1ead

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"hooks": [
{
"type": "command",
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/stop_firewall.py",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use exec-form args for the hook path

Claude Code runs hook commands without args through a shell, and its hook docs call out ${CLAUDE_PLUGIN_ROOT} paths should be passed via exec-form args or quoted so they remain one argument. If the plugin cache/install path contains spaces or shell-special characters, this command is tokenized before Python starts, so the Stop hook never runs and false Succeeded contracts are allowed; register it as command: "python3" with args: ["${CLAUDE_PLUGIN_ROOT}/hooks/stop_firewall.py"] or quote the placeholder.

Useful? React with 👍 / 👎.

Comment thread hooks/stop_firewall.py
return [exe]
root = os.environ.get("CLAUDE_PLUGIN_ROOT", "")
if root and (Path(root) / "loop" / "__main__.py").is_file():
return [sys.executable or "python3", "-m", "loop"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prevent workspace modules from shadowing the fallback CLI

When there is no loop executable on PATH, this fallback relies on python -m loop plus PYTHONPATH. Hook handlers run in the current project directory, and Python searches that directory before PYTHONPATH, so any target repo with its own top-level loop/ package will execute that project module instead of this plugin's doctor CLI; because the hook fails open, a lying contract in that repo is silently allowed. Invoke the plugin CLI by an absolute path or otherwise ensure the plugin root wins over the workspace.

Useful? React with 👍 / 👎.

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.

2 participants