Skip to content

feat: shared nudge-pacing primitive for coaching rules + PostToolUseFailure hook - #364

Merged
getappz merged 16 commits into
masterfrom
task/396
Jul 29, 2026
Merged

getappz merged 16 commits into
masterfrom
task/396

Conversation

@getappz

@getappz getappz commented Jul 29, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Adds src/nudge_pace.rs: a shared cooldown-based pacing primitive (should_fire/mark_fired, flat JSON state file, fail-open, atomic writes) so nudge text doesn't repeat in full on every matching call.
  • Coaching rules gain an optional per-rule cooldown_secs header field; rule_bodies_for_tool/rule_bodies_for_prompt are now paced through it (falling back to a 300s default). Enforced/MANDATORY rules are explicitly never paced.
  • PostToolUseFailure converts from a 15s LLM-prompt hook (a model call on every Bash/Edit/Write failure) to a deterministic agentflare hook post-tool-failure command hook, classifying via the existing vent::classify::classify() heuristic — no model call — and gated by the same pacing primitive.

Fixes from the final whole-branch review

  • Critical: the new hook was emitting hookSpecificOutput.permissionDecisionReason, which per the hooks schema only applies to PreToolUse. PostToolUseFailure's only event-specific field is additionalContext — the nudge was being silently dropped while the cooldown was still burned. Fixed and live-verified end-to-end (confirmed the nudge actually reaches context on a real failure).
  • User-initiated interrupts (is_interrupt: true) are no longer classified as friction.
  • nudge_pace's state-file write is now atomic (tmp file + rename), since it runs from short-lived hook processes that can be killed mid-write under their timeout.
  • Added a test proving a custom cooldown_secs is actually honored, not just the default.

Test plan

  • cargo build --workspace --all-features — clean
  • cargo clippy --workspace --all-targets --all-features -- -D warnings -A unsafe_code -A clippy::pedantic — clean
  • cargo fmt --check — clean
  • cargo test --workspace — 893/893 passing
  • Live-verified against a real Claude Code session: confirmed the actual PostToolUseFailure stdin payload field name (error), confirmed sub-second hook latency (vs. the old 15s LLM call), and confirmed the additionalContext nudge genuinely surfaces to the model on a real tool failure.

Summary by CodeRabbit

  • New Features
    • Added a post-tool-failure hook that extracts failure details and routes structured vent output for reporting guidance.
    • Added per-rule cooldowns for coaching rules, including preserving cooldown when rules are refreshed.
  • Bug Fixes
    • Reduced repeated coaching and tool-suggestion outputs using cooldown-based pacing (while keeping enforced guidance available).
    • Improved robustness of failure handling by safely ignoring malformed/unparseable inputs and treating corrupt pacing state as “allow”.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds persistent nudge cooldown tracking, per-rule coaching cooldown serialization and enforcement, and a new PostToolFailure CLI hook that classifies failures and emits vent guidance.

Changes

Cooldown and failure hooks

Layer / File(s) Summary
Persistent nudge pacing
src/main.rs, src/nudge_pace.rs
Registers shared pacing support and persists per-key timestamps with cooldown checks, pruning, fail-open reads, and atomic state writes.
Coaching rule cooldown contracts and retrieval
src/coaching/rule.rs, src/coaching/store.rs, src/coaching/mod.rs
Adds optional serialized cooldowns, exposes apply_rule_with_cooldown, preserves cooldowns across updates, and suppresses repeated tool- or prompt-triggered rule bodies.
Post-tool failure processing
src/hook.rs, src/vent/capture.rs
Parses failure payloads, handles interruption and malformed input, classifies failures, applies pacing, emits vent-call context, and counts recent topic matches.
Post-tool failure CLI dispatch and hook wiring
src/cli/hook.rs, src/init.rs, src/rule_text.rs
Adds the PostToolFailure event, wires matcher-based command hooks, removes the obsolete judge prompt, and dispatches to the failure handler.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HookHost
  participant CLIHook
  participant FailureHook
  participant Vent
  participant NudgePace
  HookHost->>CLIHook: invoke PostToolFailure
  CLIHook->>FailureHook: dispatch resolved agent
  FailureHook->>FailureHook: parse and classify failure
  FailureHook->>NudgePace: check and record cooldown
  FailureHook->>Vent: build vent guidance context
  FailureHook-->>HookHost: print hookSpecificOutput
Loading

Possibly related PRs

  • getappz/agentflare#213: Modifies the coaching rule selection paths that this change extends with cooldown pacing.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: shared pacing for coaching rules and the new PostToolUseFailure hook.
Description check ✅ Passed The description covers Summary and Test plan well and is mostly complete, though Notes for reviewers is not filled in explicitly.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task/396

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.

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@src/nudge_pace.rs`:
- Around line 24-70: Protect the entire load–prune–insert–save sequence in
mark_fired with an exclusive file lock scoped to the nudge-pace state file,
reusing the existing RulesLock locking pattern from coaching/store.rs. Acquire
the lock before load() and release it only after save() completes, while
preserving the current best-effort behavior and pruning logic.
- Around line 60-70: Update mark_fired so timestamp pruning does not use the
hardcoded 86,400-second retention window; retain entries for at least the
maximum cooldown supported by configured coaching rules, including arbitrary
cooldown_secs values parsed by parse_rule_file and consumed by
rule_bodies_for_tool/rule_bodies_for_prompt. Preserve should_fire behavior for
both coaching rules and vent-nudge keys while avoiding premature removal during
unrelated mark_fired calls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fbfaaba0-26c1-42e7-b844-0820d2e6d07e

📥 Commits

Reviewing files that changed from the base of the PR and between 615674f and eec8d65.

📒 Files selected for processing (8)
  • src/cli/hook.rs
  • src/coaching/mod.rs
  • src/coaching/rule.rs
  • src/coaching/store.rs
  • src/hook.rs
  • src/main.rs
  • src/nudge_pace.rs
  • src/vent/capture.rs

Comment thread src/nudge_pace.rs
Comment thread src/nudge_pace.rs
PostToolUseFailure was still registered as the old 15s prompt-type hook in
wire_claude_code(), so the new classify()-based command hook this branch
added was never reachable from a real settings.json. add_hook_entry now
takes an optional matcher so the command-hook path can carry the same
Bash|Edit|Write scoping the prompt hook had; add_prompt_hook_entry and the
now-unused VENT_JUDGE_PROMPT are removed.

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/init.rs (1)

857-863: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the matcher structurally.

This only checks for the command substring. Parse the settings JSON and assert the PostToolUseFailure entry has matcher: "Bash|Edit|Write" so a regression cannot broaden failure-hook execution to every tool.

🤖 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 `@src/init.rs` around lines 857 - 863, Update the test function
wire_claude_code_wires_post_tool_use_failure_command_hook to parse settings.json
as JSON and inspect the PostToolUseFailure hook entry structurally. Assert its
matcher equals "Bash|Edit|Write" while retaining verification of the command
hook marker.
🤖 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.

Inline comments:
In `@src/init.rs`:
- Around line 236-270: Update the hook wiring around add_hook_entry for
PostToolUseFailure to detect and remove the known legacy prompt entry before
adding the deterministic hook, preventing both entries from running on upgraded
installs. Add an upgrade test that seeds the legacy PostToolUseFailure prompt
entry and verifies it is removed and replaced by the marked hook
post-tool-failure entry.

---

Nitpick comments:
In `@src/init.rs`:
- Around line 857-863: Update the test function
wire_claude_code_wires_post_tool_use_failure_command_hook to parse settings.json
as JSON and inspect the PostToolUseFailure hook entry structurally. Assert its
matcher equals "Bash|Edit|Write" while retaining verification of the command
hook marker.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f633c0a-2324-4324-bfeb-3bc2d48b3d0e

📥 Commits

Reviewing files that changed from the base of the PR and between eec8d65 and 192959d.

📒 Files selected for processing (2)
  • src/init.rs
  • src/rule_text.rs
💤 Files with no reviewable changes (1)
  • src/rule_text.rs

Comment thread src/init.rs
getappz added 2 commits July 29, 2026 09:25
…cooldowns

Two issues flagged by CodeRabbit on the initial post-tool-failure hook PR:

- mark_fired's load-mutate-save wasn't serialized across processes, so two
  hook processes racing on it (even for different keys) could each load()
  before the other's save() lands, silently dropping a fired-timestamp.
  Added PaceLock, same create_new-sentinel pattern as coaching's RulesLock.
- mark_fired pruned any entry older than a hardcoded 24h regardless of the
  key's own cooldown_secs, which coaching rules can set arbitrarily high via
  their '# Cooldown:' header. Raised the prune window to 30 days so it can't
  outrun a configured cooldown.
add_hook_entry only skips adding when its own marker is already present, so
an install wired by an older agentflare version (still carrying the
'genuine FRICTION' prompt-type hook) would get the new command hook added
beside it instead of replacing it -- both firing on every tool failure.
remove_hook_entries_matching() strips the legacy entry before the new one
is wired.
@getappz
getappz enabled auto-merge (squash) July 29, 2026 04:16
@getappz
getappz merged commit a041ac4 into master Jul 29, 2026
15 checks passed
@getappz
getappz deleted the task/396 branch July 29, 2026 04:22
getappz added a commit that referenced this pull request Oct 16, 2026
…ed repo (#364) (#909)

Agentflare-Agent: claude-code
Agentflare-Branch: fix/364-base-repo-discovery-test
Agentflare-Session: a0b8d371-2751-4c9f-98f6-017eeff433ea
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