Skip to content

fix(agent-status): clear answered Claude question waits - #8311

Merged
nwparker merged 1 commit into
stablyai:mainfrom
mmarabel:gatsby74/stuck-needsattention
Jul 11, 2026
Merged

nwparker merged 1 commit into
stablyai:mainfrom
mmarabel:gatsby74/stuck-needsattention

Conversation

@mmarabel

@mmarabel mmarabel commented Jul 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Clear Claude's needs-attention state after the user answers an interactive question and subsequent tool work begins.

AskUserQuestion waits arrive as PreToolUse, but the hook server previously treated every Claude waiting → working transition as a sticky permission request. A later Read, Bash, or Edit event therefore could not replace the stale wait unless it matched the original tool identity.

This change limits sticky retention to actual PermissionRequest events. Answered interactive questions can return to the existing working state, while genuine permission requests remain visible during unrelated tool activity.

Closes #8310.

Screenshots

No visual change. The existing animated working indicator now resumes at the correct state transition.

Prior bug captured example:
image

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test
  • pnpm build
  • Added or updated high-quality tests that would catch regressions, or explained why tests were not needed

Focused validation completed with Node 24:

  • pnpm exec vitest run --config config/vitest.config.ts src/main/agent-hooks/server.claude-interactive-question.test.ts src/main/agent-hooks/server.test.ts src/shared/agent-hook-listener.test.ts --maxWorkers=1 — 319 tests passed
  • pnpm run typecheck:node
  • pnpm exec oxfmt --check src/main/agent-hooks/server.ts src/main/agent-hooks/server.claude-interactive-question.test.ts
  • pnpm exec oxlint src/main/agent-hooks/server.ts src/main/agent-hooks/server.claude-interactive-question.test.ts
  • pnpm run check:max-lines-ratchet

The full repository lint, typecheck, test, and build commands were not run locally; the corresponding checklist items remain unchecked.

AI Review Report

Reviewed the final two-file diff and the surrounding Claude permission-retention logic.

  • Cross-platform: The change adds one event-name comparison in platform-neutral TypeScript. It does not touch shortcuts, labels, paths, shells, or Electron platform APIs, so behavior is consistent on macOS, Linux, and Windows.
  • Local and SSH/remote: Both local HTTP hook events and remote relay events converge on the same applyNormalizedStatus guard. The regression test exercises the remote ingestion path; the existing shared normalization and server suites cover the local normalization and permission behavior.
  • Agents and integrations: The guard remains scoped to agentType === 'claude'. Other supported agents and integrations are unchanged. A control test verifies that genuine Claude PermissionRequest state remains sticky during unrelated tool work.
  • Performance: The hot-path impact is a single string comparison only during Claude waiting → working transitions. No polling, allocation, or rendering work was added.
  • UI quality: No layout, styling, copy, or component behavior changed. The fix only selects the already-existing working status at the correct time.

No additional issues were found during review.

Security Audit

The change introduces no new commands, filesystem paths, dependencies, authentication behavior, secrets, network requests, or IPC surfaces. It compares the already-normalized hookEventName against the existing PermissionRequest event name before deciding whether to retain cached status. Input validation and trust-boundary normalization remain unchanged.

No security follow-up is needed.

Notes

  • Reproduced with Claude on a local macOS worktree in Orca 1.4.136-rc.0.
  • The corrected transition is shared by local and SSH/remote hook ingestion.
  • Pressing Esc and submitting another prompt already reset the stale status because those actions created explicit state boundaries; this fix handles the normal answer-and-continue path directly.
  • X (Twitter) handle: not provided.

@mmarabel
mmarabel marked this pull request as ready for review July 11, 2026 18:43
@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Claude permission visibility now remains sticky only when the previous hook was a PermissionRequest; interactive-question waits clear on a subsequent working hook. A new Vitest suite adds shared event-ingestion setup and verifies both status-transition behaviors through getStatusSnapshot().

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The change and regression tests satisfy #8310 by clearing AskUserQuestion waits and keeping PermissionRequest waits sticky.
Out of Scope Changes check ✅ Passed The diff stays focused on the linked bug fix and its tests, with no unrelated feature or UI changes.
Title check ✅ Passed The title clearly matches the main change: clearing answered Claude question waits and preserving genuine permission requests.
Description check ✅ Passed The description follows the template with summary, screenshots, testing, AI review, security audit, and notes, including cross-platform review.

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.

@nwparker nwparker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks — this is exactly the right scope for the stuck needs-attention bug.

Reviewed and verified end to end:

  • Root cause confirmed: the sticky guard exists to protect PermissionRequest waits (no tool_use_id), and was over-applied to every Claude waiting→working transition, trapping answered AskUserQuestion waits. Scoping it by originating hook event is the correct fix, and it strictly narrows stickiness — a genuine permission wait still survives unrelated subagent activity.
  • Rebased on latest main locally: the new test goes red when the server change is reverted, and the three affected suites (319 tests) pass with it. Typecheck/lint/format/ratchet clean.
  • Verified live in a dev build with a real Claude session plus a replay of the exact hook sequence through the local HTTP ingestion path: question pending shows the amber needs-attention dot; an unrelated Read working hook now flips the sidebar to the working spinner (previously stuck until Esc); a real PermissionRequest stays amber through unrelated tool work; Stop resolves to done.

@nwparker
nwparker merged commit 745b164 into stablyai:main Jul 11, 2026
5 checks passed
@mmarabel

Copy link
Copy Markdown
Contributor Author

Hey @nwparker I'm still experiencing this in certain cases - I've added a follow up PR with some further tweaks: #8383

brennanb2025 added a commit that referenced this pull request Jul 16, 2026
An answered AskUserQuestion left the amber "waiting" indicator on sidebar
rows and tabs until the agent's next tool hook or turn end — unbounded
linger while the model thinks or streams after the answer (measured 17s
for a 1000-word reply, 44s for 3000 words).

Root cause is an event-shape change: newer Claude reports the
AskUserQuestion wait as PermissionRequest (not the PreToolUse shape #7852
special-cased), so the wait inherited real-permission stickiness and
shouldKeepClaudePermissionVisible swallowed the answer-time
PostToolUse(AskUserQuestion) working event — the identity match can never
succeed because the question's PermissionRequest carries no inheritable
tool_use_id. That silently undid #8311 for questions.

Two scoped changes, both keyed on the tool name rather than the hook
event name:

- Sticky permission hold now exempts AskUserQuestion waits, so the real
  answer-time hook (when Claude sends one) clears the wait as #8311
  intended.
- New guarded inference for the hook Claude may never send: the submit
  keystroke (Enter or digit quick-select) into a pane whose fresh status
  is a waiting AskUserQuestion synthesizes the post-answer state, exactly
  mirroring the existing interrupt inference (renderer baseline capture,
  main-process re-validation, listener lead-state sync so child-driven
  refreshes cannot resurrect the dismissed question).

Real permission waits (other tools) keep their sticky semantics; batched
input and pastes never match the submit classifier.

Verified live against a real claude CLI: waiting -> working within ~50ms
of both Enter and digit answers, question card dropped, unanswered
questions still hold amber, permission stickiness covered by tests.
brennanb2025 added a commit that referenced this pull request Jul 17, 2026
#9074)

* fix(agent-status): clear answered Claude question waits at answer time

An answered AskUserQuestion left the amber "waiting" indicator on sidebar
rows and tabs until the agent's next tool hook or turn end — unbounded
linger while the model thinks or streams after the answer (measured 17s
for a 1000-word reply, 44s for 3000 words).

Root cause is an event-shape change: newer Claude reports the
AskUserQuestion wait as PermissionRequest (not the PreToolUse shape #7852
special-cased), so the wait inherited real-permission stickiness and
shouldKeepClaudePermissionVisible swallowed the answer-time
PostToolUse(AskUserQuestion) working event — the identity match can never
succeed because the question's PermissionRequest carries no inheritable
tool_use_id. That silently undid #8311 for questions.

Two scoped changes, both keyed on the tool name rather than the hook
event name:

- Sticky permission hold now exempts AskUserQuestion waits, so the real
  answer-time hook (when Claude sends one) clears the wait as #8311
  intended.
- New guarded inference for the hook Claude may never send: the submit
  keystroke (Enter or digit quick-select) into a pane whose fresh status
  is a waiting AskUserQuestion synthesizes the post-answer state, exactly
  mirroring the existing interrupt inference (renderer baseline capture,
  main-process re-validation, listener lead-state sync so child-driven
  refreshes cannot resurrect the dismissed question).

Real permission waits (other tools) keep their sticky semantics; batched
input and pastes never match the submit classifier.

Verified live against a real claude CLI: waiting -> working within ~50ms
of both Enter and digit answers, question card dropped, unanswered
questions still hold amber, permission stickiness covered by tests.

* fix(agent-status): guard question answer inference

Keep multi-question, multi-select, and free-text selector interactions waiting until the full prompt is submitted. Wire native-chat answers into the same guarded inference only after every paced runtime write succeeds, with cancellation and delivery-failure coverage.

* chore(skills): refresh manifest for rc.2

* fix(agent-status): verify native chat answer delivery

* fix(agent-status): await verified question delivery

* fix(agent-status): pin native-chat answer baseline before delivery

The native-chat question-answered inference read the live pane status at
settle time (after the paced send + remote acceptance, which can span
seconds on SSH). If a replacement AskUserQuestion became current in that
window, the settle callback minted a fresh baseline from the new question
and the server cleared *its* wait — dismissing a question the user never
answered.

Capture the answered question's baseline before delivery and have the
inference getter return it, so the server re-validates against the pinned
baseline and rejects a changed status — the same capture-then-revalidate
contract the terminal keystroke path already uses. Also hoist the
shouldStepNativeChatAskAnswer predicate to a single evaluation.

Regression test swaps the live status between sendAnswer and settle and
asserts the answered question's baseline is used (fails against the prior
live-read getter).
mmarabel added a commit to mmarabel/orca that referenced this pull request Aug 15, 2026
…ionRequest

Claude's AskUserQuestion is auto-allowed, but in addition to the PreToolUse
hook it emits a PermissionRequest ~120ms later. Orca registers
PermissionRequest, so that event overwrites the PreToolUse as the cached
`previous` status — and it carries no tool_use_id.

shouldKeepClaudePermissionVisible (from stablyai#8311) keeps a Claude
PermissionRequest wait sticky until a resuming-tool id match clears it. Since
the AskUserQuestion PermissionRequest exposes no tool_use_id, the answer's
PostToolUse 'working' hook can never satisfy that match, so the sidebar row
stays pinned on the amber "waiting" attention state until the turn's Stop
flips it straight to done. Users see the row appear stuck from the moment
they answer until the whole reply finishes.

lands right after it slips through. Treat any PermissionRequest whose tool is
an AskUserQuestion variant as an interactive question rather than an
Allow/Deny gate, so its wait clears on the next 'working' hook. Real tool
permission requests (Bash, etc.) stay sticky as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SCLYfTSDnDyBgotAp8yRoV
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
stablyai#9074)

* fix(agent-status): clear answered Claude question waits at answer time

An answered AskUserQuestion left the amber "waiting" indicator on sidebar
rows and tabs until the agent's next tool hook or turn end — unbounded
linger while the model thinks or streams after the answer (measured 17s
for a 1000-word reply, 44s for 3000 words).

Root cause is an event-shape change: newer Claude reports the
AskUserQuestion wait as PermissionRequest (not the PreToolUse shape stablyai#7852
special-cased), so the wait inherited real-permission stickiness and
shouldKeepClaudePermissionVisible swallowed the answer-time
PostToolUse(AskUserQuestion) working event — the identity match can never
succeed because the question's PermissionRequest carries no inheritable
tool_use_id. That silently undid stablyai#8311 for questions.

Two scoped changes, both keyed on the tool name rather than the hook
event name:

- Sticky permission hold now exempts AskUserQuestion waits, so the real
  answer-time hook (when Claude sends one) clears the wait as stablyai#8311
  intended.
- New guarded inference for the hook Claude may never send: the submit
  keystroke (Enter or digit quick-select) into a pane whose fresh status
  is a waiting AskUserQuestion synthesizes the post-answer state, exactly
  mirroring the existing interrupt inference (renderer baseline capture,
  main-process re-validation, listener lead-state sync so child-driven
  refreshes cannot resurrect the dismissed question).

Real permission waits (other tools) keep their sticky semantics; batched
input and pastes never match the submit classifier.

Verified live against a real claude CLI: waiting -> working within ~50ms
of both Enter and digit answers, question card dropped, unanswered
questions still hold amber, permission stickiness covered by tests.

* fix(agent-status): guard question answer inference

Keep multi-question, multi-select, and free-text selector interactions waiting until the full prompt is submitted. Wire native-chat answers into the same guarded inference only after every paced runtime write succeeds, with cancellation and delivery-failure coverage.

* chore(skills): refresh manifest for rc.2

* fix(agent-status): verify native chat answer delivery

* fix(agent-status): await verified question delivery

* fix(agent-status): pin native-chat answer baseline before delivery

The native-chat question-answered inference read the live pane status at
settle time (after the paced send + remote acceptance, which can span
seconds on SSH). If a replacement AskUserQuestion became current in that
window, the settle callback minted a fresh baseline from the new question
and the server cleared *its* wait — dismissing a question the user never
answered.

Capture the answered question's baseline before delivery and have the
inference getter return it, so the server re-validates against the pinned
baseline and rejects a changed status — the same capture-then-revalidate
contract the terminal keystroke path already uses. Also hoist the
shouldStepNativeChatAskAnswer predicate to a single evaluation.

Regression test swaps the live status between sendAnswer and settle and
asserts the answered question's baseline is used (fails against the prior
live-read getter).
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.

[Bug]: Claude worktree status stays on needs attention after answering a question

2 participants