Skip to content

feat: plugin hooks no-op outside a bstack workspace — scope-safe global plugin (BRO-1926 Phase 2 prereq)#95

Merged
broomva merged 2 commits into
mainfrom
feature/bro-1926-plugin-scope-guard-bootstrap
Jul 16, 2026
Merged

feat: plugin hooks no-op outside a bstack workspace — scope-safe global plugin (BRO-1926 Phase 2 prereq)#95
broomva merged 2 commits into
mainfrom
feature/bro-1926-plugin-scope-guard-bootstrap

Conversation

@broomva

@broomva broomva commented Jul 16, 2026

Copy link
Copy Markdown
Owner

What

Makes the bstack plugin (v0.35.0) a good citizen at personal scope, so it can be adopted in place of hand-wired settings.json hooks without polluting non-bstack repos.

  • New hooks/bstack-hook-guard.sh — gates each hook on the workspace being bstack-governed (has a .control/ dir). Non-governed → no-op (PreToolUse still emits {"decision":"approve"} so tools aren't left undecided).
  • hooks/hooks.json — routes the 5 workspace hooks (knowledge-wakeup, arc-continuation, leverage-sensor, autonomous-posture, l3-stability) through the guard. bstack-autoupdate stays unguarded (keeps the install fresh, workspace-agnostic).
  • tests/hook-guard.test.sh — 4 cases (no-op outside / approve for PreToolUse / runs inside / sibling $0 resolution preserved).
  • VERSION 0.35.0 → 0.36.0 · CHANGELOG.

Why

The plugin loads at personal scope (~/.claude/skills/bstack), so once enabled its hooks fire in every session, including non-bstack repos. leverage-sensor.py/leverage-ship-sensor.py do os.makedirs(<ws>/.control, exist_ok=True) + write, and l3-stability-pretool logs to <ws>/.control/audit/. Unguarded, a global plugin litters .control/ into unrelated repos (e.g. a client repo) every session. The guard fixes all current + future writers in one place.

Design notes

  • Sibling $0/dirname resolution is preserved — the guard execs the real script by full path (unit-tested).
  • No behavior change in a bstack workspace (.control/ is always present there).
  • Single-point guard chosen over N per-script source guards (robust to future writers).

Validation

  • shellcheck hooks/bstack-hook-guard.sh tests/hook-guard.test.sh: clean (one intentional SC2016 disabled)
  • bash tests/hook-guard.test.sh: 4/4 pass
  • jq -e . hooks.json + claude plugin validate: pass
  • VERSION ↔ CHANGELOG aligned

Ticket: BRO-1926 Phase 2 prerequisite. (Phase 2 = local adoption; Phase 3 = bstack bootstrap prefers the plugin.)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added workspace-aware safeguards for governance hooks.
    • Hooks now skip execution in non-governed workspaces while still returning safe tool decisions.
    • Preserved normal hook execution and script path handling in governed workspaces.
  • Bug Fixes

    • Prevented tools from remaining undecided when hooks are skipped.
  • Tests

    • Added coverage for governed and non-governed workspace behavior, decision responses, and script resolution.
  • Chores

    • Updated the release version to 0.36.0 and documented the changes.

…al plugin (BRO-1926)

The plugin loads at personal scope (~/.claude/skills/bstack), so once a workspace
enables bstack@skills-dir its hooks fire in every session, including non-bstack
repos. leverage-sensor.py/leverage-ship-sensor.py makedirs+write <ws>/.control/*.json
and l3-stability-pretool logs to <ws>/.control/audit/ — so a global plugin would
litter .control/ into unrelated repos (e.g. a client repo) every session.

New hooks/bstack-hook-guard.sh gates each hook on the workspace being bstack-governed
(has .control/); if not, no-op (PreToolUse still emits approve). hooks/hooks.json routes
the 5 workspace hooks through the guard; bstack-autoupdate stays unguarded (workspace-
agnostic). Sibling $0/dirname resolution preserved (guard execs the real script by full
path). tests/hook-guard.test.sh covers 4 cases. No behavior change in a bstack workspace.

Prerequisite for BRO-1926 Phase 2 (adopting the plugin over hand-wired settings.json hooks).
Bump 0.35.0 -> 0.36.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@broomva, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0db44241-0966-4acd-90f9-25c67dd27e6e

📥 Commits

Reviewing files that changed from the base of the PR and between 63fb63b and 533355b.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • hooks/bstack-hook-guard.sh
  • hooks/hooks.json
  • scripts/l3-stability-pretool-hook.sh
  • tests/hook-guard.test.sh
📝 Walkthrough

Walkthrough

The plugin adds a workspace guard that skips governed hooks outside .control/ workspaces, emits approval decisions for skipped pretool hooks, and executes commands unchanged in governed workspaces. Hook registrations, tests, version metadata, and changelog entries are updated.

Changes

Hook scope safety

Layer / File(s) Summary
Guard behavior and validation
hooks/bstack-hook-guard.sh, tests/hook-guard.test.sh
The guard resolves workspace scope, checks .control/, handles skipped pretool hooks, preserves command execution paths, and validates four governed/non-governed cases.
Hook registration and release metadata
hooks/hooks.json, VERSION, CHANGELOG.md
Workspace hook commands invoke the guard, and release metadata documents version 0.36.0 and the guarded behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ClaudeCode
  participant hooks.json
  participant bstack-hook-guard.sh
  participant WrappedHook
  ClaudeCode->>hooks.json: Trigger workspace hook
  hooks.json->>bstack-hook-guard.sh: Invoke guard with hook command
  bstack-hook-guard.sh->>bstack-hook-guard.sh: Resolve workspace and check .control/
  alt Governed workspace
    bstack-hook-guard.sh->>WrappedHook: exec command and arguments
    WrappedHook-->>ClaudeCode: Hook output
  else Non-governed pretool hook
    bstack-hook-guard.sh-->>ClaudeCode: Return approve decision
  else Non-governed generic hook
    bstack-hook-guard.sh-->>ClaudeCode: No-op
  end
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: hooks no-op outside bstack workspaces and the plugin becomes scope-safe.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/bro-1926-plugin-scope-guard-bootstrap

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
tests/hook-guard.test.sh (1)

66-66: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Append failure context to the assertion message.

For consistency with the other test cases in this file, consider appending a brief explanation of the failure state to the failure message.

💡 Proposed change
-    assert_fail "governed workspace: sibling \$0/dirname resolution preserved"
+    assert_fail "governed workspace: sibling \$0/dirname resolution preserved (it did not)"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/hook-guard.test.sh` at line 66, Update the governed workspace assertion
using assert_fail to append a brief explanation of the expected failure state,
matching the contextual failure-message style used by the other test cases in
tests/hook-guard.test.sh.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/hook-guard.test.sh`:
- Line 66: Update the governed workspace assertion using assert_fail to append a
brief explanation of the expected failure state, matching the contextual
failure-message style used by the other test cases in tests/hook-guard.test.sh.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b0e15a4b-43cb-410c-ba89-0cd7efb633cd

📥 Commits

Reviewing files that changed from the base of the PR and between e2232fc and 63fb63b.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • VERSION
  • hooks/bstack-hook-guard.sh
  • hooks/hooks.json
  • tests/hook-guard.test.sh

…it nothing (P20)

P20 review (7/10, 1 MAJOR) found that wrapping the session-scoped, self-guarding,
non-polluting arc-continuation + autonomous-posture hooks in a cwd-.control gate
FALSE-NEGATIVES the loop-stall/posture logic when cwd is a nested non-governed git
repo (work/stimulus, core/autoany confirmed present) — for zero pollution benefit.

Redesign, matched to actual hook behavior:
- Guard wraps ONLY the two workspace-.control writers: knowledge-wakeup (SessionStart,
  runs leverage-ship-sensor) + leverage-sensor (Stop). Once-per-event → negligible git cost.
- arc-continuation + autonomous-posture run UNWRAPPED (session-scoped, self-guarding,
  write nothing to <ws>/.control). Fixes the MAJOR false-negative.
- l3-stability source-guarded internally (approve + no .control/audit when ungoverned);
  reached only for L3-file edits, so NO per-edit git cost (MINOR fix).
- Guard emits nothing (removed the {"decision":"approve"} case) — no PreToolUse hook
  goes through it anymore, and exit-0-no-output is the correct neutral (MINOR fix).
- Guard exec's the real script by full path: exit codes + stdout + $0/dirname pass through.

Test grows 4→7 cases: adds exit-code propagation, stdout passthrough, and l3 source-guard
both directions. CHANGELOG rewritten to match + fix the leverage-ship-sensor wording.

Round-2 of BRO-1926 Phase 2 prereq PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@broomva
broomva merged commit cd0af6c into main Jul 16, 2026
6 checks passed
@broomva
broomva deleted the feature/bro-1926-plugin-scope-guard-bootstrap branch July 16, 2026 21:05
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