Skip to content

feat: AskUserQuestion hook interceptor for clarification resolution - #825

Open
davidmiuraactualai wants to merge 33 commits into
mainfrom
feat/clarification-interceptor
Open

feat: AskUserQuestion hook interceptor for clarification resolution#825
davidmiuraactualai wants to merge 33 commits into
mainfrom
feat/clarification-interceptor

Conversation

@davidmiuraactualai

Copy link
Copy Markdown

Summary

  • Adds AskUserQuestion as AdvisorGated in classify_tool_action() — interception posts to the advisor intervention API
  • Registers AskUserQuestion as an evaluation boundary in is_evaluation_boundary()
  • Maps PreToolUse:AskUserQuestion to event code actual.event.user.questioned in canonicalize
  • Adds AskUserQuestion matcher to DEFAULT_HOOK_ENTRIES in setup (timeout: 30s)
  • All existing tests updated, new tests added for each change

Test plan

  • cargo test --lib observe — all boundary, canonicalize, and setup tests pass
  • Integration: deploy with sprintreview clarification resolver PR for end-to-end testing

🤖 Generated with Claude Code

davidmiuraactualai and others added 30 commits August 2, 2026 22:13
Introduces an ephemeral OAuth flow that authenticates without storing a
refresh token, then calls POST /repos/onboard-public to queue a public
repository for architecture analysis.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…es 1C, 4A, 4C)

Phase 1C: `actual observe` subcommand with 8 hook types (session-start,
prompt, pre-tool, post-tool, post-tool-failure, stop, session-end,
pre-compact). Deterministic canonicalization, JSONL session journal,
typed hook payloads.

Phase 4A: Evaluation boundary detection — PreToolUse(Edit|Write),
PostToolUseFailure, Stop are boundaries. Hook output builder for
silent/inform/warn/block dispositions with Claude Code hook contract.

Phase 4C: `actual observe setup` installs 8 hooks into
.claude/settings.json additively (preserves existing hooks, idempotent).
`actual observe status` reports hook installation state.

50 tests across boundary, canonicalize, hook_output, journal, types,
and setup modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude Code validates hooks as {matcher, hooks: [{type, command}]},
not flat {type, command} entries. Fixes settings validation error
on session start.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
At evaluation boundaries (file edits, tool failures, session stops),
the observer now loads stored credentials, reads journal events, and
POSTs to the advisor intervention API. Non-boundary hooks remain
zero-latency. Any error degrades silently to empty hook output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a JSONL line fails to parse, print a warning to stderr with the
line number and error before skipping, so users know an event was lost.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The advisor intervention endpoint requires the observe:events scope,
but the CLI login flow only requested adr:query and adr:review. Without
this scope, observer boundary evaluations get 403 from the API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add app_url parameter to loopback server success page
- Thread base_url from config to both login_with and login_ephemeral
- Show styled "Open web app" button on the signed-in callback page
- Add reset command stub and CLAUDE.md updates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The API only accepts HTTPS URLs, but users naturally pass SSH URLs
(git@github.com:owner/repo.git). Convert at the CLI boundary so both
formats work seamlessly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The API's onboard-public endpoint now handles SSH URLs directly, so
the CLI passes the user's URL as-is.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Checks service ports (Supabase, Temporal, Redis, API, Next.js), env
files, .next cache health, Python venv, and key env vars. Outputs
numbered copy-paste fix commands for any failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Check login status, token expiry, and permission scopes. Displays
auth state before services section so users see credential issues
first.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
UserPromptSubmit triggers advisor evaluation for substantial prompts
(>= 20 chars), filtering out short confirmations like "yes" or "ok".
Agent PreToolUse (launch) is a conditional boundary requiring a
description or prompt. Agent PostToolUse (return) is unconditional.
New AEWO codes: actual.event.agent.delegated and
actual.event.agent.returned for orchestration classification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The observe command loaded stored credentials but never checked if the
access token was expired. When the token expired, the API returned 401
which degraded to silent — effectively disabling all boundary evaluations
without any clear signal to the user.

Add ensure_fresh() (matching the advisor command's existing pattern) to
refresh the access token via the OAuth refresh flow before making the API
call. When no refresh token is available, fail explicitly with NotLoggedIn
so the error message tells the user to re-login.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace internal dev stack checks (Supabase ports, Redis, pnpm monorepo,
.next cache, Python venv, env vars) with checks relevant to any user:

- Authentication: signed in, token not expired, required scopes
- Repository: inside a git repo with an origin remote
- Observer: hooks installed (project or global .claude/settings.json)
- Connectivity: configured API endpoint is reachable

The command now works correctly from any repo, not just the sprintreview
monorepo.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CLI now chunks events into batches of 50 at evaluation boundaries, sends
each chunk sequentially (blocking), and merges all non-silent responses
ranked by severity (block > warn > inform) into a single combined
hook_output. This prevents HTTP 413s, avoids Claude Code timeouts, and
ensures no interjection is missed across chunks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The advisor pipeline takes 2-4+ minutes for deep agent LLM round-trips.
The 10s timeout was causing the CLI to give up before the workflow completed.
Set to 480s (8min) to fit inside the 10min Claude Code hook timeout, with
the API safety timeout at 7.5min nested inside.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Hook timeout 600s → 1200s to accommodate 15min API pipeline timeout
- Add SubagentStart hook event and subagent-tool CLI subcommand
- Inject hookEventName into hook output for Claude Code validation
- Update tests for 9 hooks and 1200s timeout assertion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Transform BLOCK disposition from advisory-only text injection into actual
tool call prevention using Claude Code's decision:"block" hook protocol.
Add mutating Bash command detection to evaluation boundaries so rm, git
push, pnpm install, etc. are gated alongside Edit/Write.

Phase 1 of the Architecture Gate Before Mutation strategy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 2: Architecture lease system with local LeaseStore, LeaseChecker
for millisecond-fast PreToolUse decisions, and tool action classification
(Free/LeaseGated/AdvisorGated).

Phase 3: CLI plumbing for architecture brief at UserPromptSubmit.

Phase 4: Git diff capture in Stop hook for continuous architecture
verification. Invalidates lease on block disposition.

Phase 5: Matcher-specific PreToolUse hooks (Edit|Write 30s, Bash 30s,
Agent 600s) so Read/Grep never fire the hook.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix block disposition silently dropped when API returns non-object hook_output
- Lowercase curl mutation patterns so they match lowercased commands
- Inject hook_type into journal events so API receives correct event type
- HTML-escape app_url in OAuth loopback to prevent injection
- Replace naive ">" substring check with smarter redirect detection (skip 2>&1)
- Verify actual hook entries in exec_status instead of just file existence
- Use build_authorize_url_with_path in login_ephemeral to eliminate duplication

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Rust type system for Progressive Governance System interactions:
- GovernanceDecision enum (6 variants, SCREAMING_SNAKE_CASE serde)
- PolicyStrength enum (5 RFC 2119 strengths)
- FindingSeverity enum (violation, concern, observation)
- NormativePolicyStatement, ConformanceFinding, Proposal, BoundedAuthorization
- GovernanceDecisionResponse with full decision payload
- GovernanceState for session-level governance persistence
- AdditionalContext, DecompositionGuidance for progressive disclosure
- 16 passing unit tests for serde round-trips and field handling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds RetrievalAffordance, EnhancedBrief structs, Display impl for
PolicyStrength, and format_brief_output function that renders normative
policy strength as [MUST], [SHOULD], etc. 8 new tests (24 total).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds PlanCaptureResult enum, classify_decision_for_hook mapping all 6
decision types, and format_governance_block_output for rich feedback on
blocked plans. Exhaustive match arms for future-proofing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adversarial review found two logic bugs in record_decision:
1. rework_iterations never reset on approval — second proposal cycle
   inherited count from first, causing premature escalation.
2. Approval without authorization left stale auth from prior cycle.

Adds tests for reset behavior, stale auth clearing, and
Decompose/Escalate/Deny decision recording.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ScopeClassification enum, ScopeReviewResponse, AssuranceResponse, and
AssuranceConformance types. GovernanceState gains touched_paths tracking,
needs_scope_review() and needs_assurance() checks, and format helpers for
scope review and assurance output. 10 new tests covering scope/assurance
serialization, path tracking, and state transition logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ProvenanceEventType enum (11 event types), ProvenanceEvent struct,
ProvenanceChainResponse, and format_governance_history function for
displaying ordered provenance chains. 5 new tests covering serde
round-trips and history formatting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces the old start_advisor_query + poll_to_completion path with a
single sync post_intervention call. The CLI now sends a UserPromptSubmit
event through the intervention pipeline, matching how observer hooks
deliver architecture guidance.

Removes dead code: POLL_PATH, START_BODY, MAX_RETRY_AFTER, Outcome enum,
next_delay, print_answer, and tests that exercised the removed functions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ctual-end/>

Two features:
1. `actual observe setup --localhost` bakes ACTUAL_AUTH_URL/ACTUAL_API_URL env
   vars into hook commands so all hook invocations hit local services.
2. Stop-hook loop prevention: the advisor response includes <actual-end/>,
   and the CLI sets a per-session stop_ack flag that suppresses subsequent
   Stop hook evaluations until real work (PreToolUse/boundary) occurs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wCALDeVcpBedCJ7gdKUo5
observe setup now resolves the repo's unique ID by matching the git
remote origin URL against the org's connected repos via the API, then
persists it in sticky_repo_scope. Subsequent hook invocations send
repo_unique_id instead of null, enabling repo-scoped ADR search.

- exec_setup calls try_resolve_and_persist_scope after installing hooks
- Respects --localhost flag for API URL selection
- Exposes parse_git_remote_url, match_remote_to_repos, RepoRemote as
  pub(crate) from advisor.rs for reuse in observe.rs
- Adds resolve_repo_unique_id_from_events to read sticky scope during
  hook invocations (was previously hardcoded to None)
- Degrades gracefully: if resolution fails, hooks still work at org level

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpoJGi6P75oHz33j5FAhj8
davidmiuraactualai and others added 3 commits August 25, 2026 09:58
Default `actual observe setup` now installs only 5 hooks (SessionStart,
UserPromptSubmit, PreToolUse[ExitPlanMode], Stop, SessionEnd) for lower
overhead. Use `--hook-all` to include per-tool-use, post-tool, pre-compact,
and subagent hooks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpoJGi6P75oHz33j5FAhj8
Add AskUserQuestion as an AdvisorGated boundary in the observer pipeline.
When the agent attempts to ask the user a question, the hook fires and
routes through the intervention API, enabling Actual to resolve questions
from repository evidence before they reach the developer.

- boundary.rs: classify AskUserQuestion as AdvisorGated + evaluation boundary
- canonicalize.rs: map to actual.event.user.questioned event code
- setup.rs: add AskUserQuestion to DEFAULT_HOOK_ENTRIES

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the explore phase's Stop hook returned <actual-end/>, it set
stop_acknowledged. If the agent then output questions (text only, no
tool calls), the next Stop hook would see stop_acknowledged=true and
return {} without evaluating — the clarification pipeline never ran.

Now set_stop_acknowledged stores a hash of last_assistant_message, and
is_stop_acknowledged compares it against the current message. A
different message clears the ack so every new agent output gets
evaluated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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