Skip to content

Fix #5286: surface needs-input attention for feed-routed blocking decisions - #5313

Merged
lawrencecchen merged 13 commits into
mainfrom
issue-5286-feed-permissionrequest
Jun 10, 2026
Merged

lawrencecchen merged 13 commits into
mainfrom
issue-5286-feed-permissionrequest

Conversation

@austinywang

@austinywang austinywang commented Jun 3, 2026 •

Copy link
Copy Markdown
Contributor

Problem

When Claude Code blocks mid-turn on a PermissionRequest (tool permission, ExitPlanMode, AskUserQuestion), the bundled wrapper fires cmux hooks feed --source claude, which sends a blocking feed.push and waits. cmux surfaced nothing: no sidebar "Needs input" status, no bell, no tab/workspace elevation (and no banner while the app is active). The command blocked until the 125s timeout and Claude continued without the user ever knowing it asked. The cmux hooks claude notification|stop paths work in the same setup — the break is specific to the feed decision bridge. (#5286)

Minimal local repro (no Claude Code needed)

echo '{"session_id":"manual-test","hook_event_name":"PermissionRequest","tool_name":"Write","tool_input":{"file_path":"/tmp/x"}}' \
  | "$CMUX_BUNDLED_CLI_PATH" hooks feed --source claude

Blocks waiting for a decision, but nothing surfaces in the UI.

Root cause

The reporter's diagnosis is correct. The feed.push blocking bridge and the in-app attention surface diverged during the PreToolUse→PermissionRequest migration. FeedCoordinator.ingestBlocking ingested the Feed card and (only when the app is inactive) posted a desktop banner, but never drove the central attention path — agent lifecycle needsInput + "Needs input" sidebar status + workspace reorder + bell — that the working cmux hooks <agent> notification hook uses.

This is a class of bug: every event type routed through feed (PermissionRequest, ExitPlanMode, AskUserQuestion, and future kinds) silently swallowed in-app attention.

Fix

Converge at the single choke point every feed-routed blocking decision flows through. FeedCoordinator.ingestBlocking now calls surfaceBlockingDecisionAttention(event:) for any blocking-decision event, which:

  • flips the owning workspace's agent lifecycle to .needsInput and sets the localized "Needs input" sidebar status, keyed by a source→status-key map (claude→claude_code) so the existing per-agent resume hooks (e.g. Claude's pre-tool-use) clear it once the agent continues;
  • elevates the workspace to the top of the sidebar when Reorder on Notification is enabled;
  • rings the bell (requestUserAttention).

This fires regardless of app focus. The existing inactive-app feed banner — with its inline allow/deny actions — remains the desktop surface and the decision channel. Doing the surfacing once, for every blocking-decision event, eliminates the whole class rather than patching PermissionRequest alone.

Tests

Two-commit red/green:

  1. Failing test — FeedCoordinatorTests.testBlockingIngestSurfacesNeedsInputAttentionForPermissionRequest asserts that a blocking PermissionRequest routed through ingestBlocking requests in-app attention surfacing. Red before the fix.
  2. Fix — green after.

Localization

Added feed.status.needsInput (en: "Needs input", ja: "入力待ち") to Resources/Localizable.xcstrings. No other new user-facing strings.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Medium Risk
Touches main-thread UI state, workspace targeting, and blocking-hook timing; wrong targeting or overlay cleanup could hide or stick badges, but scope is localized to FeedCoordinator with refcounting and conservative teardown.

Overview
Blocking feed.push decisions (permissions, plan approval, questions) now drive the same in-app attention path as agent notification hooks: Needs input sidebar status, .needsInput lifecycle, optional workspace reorder, and dock attention—even when the app is focused (inactive-only banners were insufficient).

FeedCoordinator resolves workspace/panel targets (preferring live workspace_id, refcounting overlapping decisions), records targets on waiters during ingest, and clears overlays on reply or timeout without clobbering agent-updated status. Adds feed.status.needsInput localization and tests for the blocking ingest path, decision predicate, and claude → claude_code status key mapping.

Reviewed by Cursor Bugbot for commit 5988f89. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Surfaces in-app “Needs input” for all feed-routed blocking decisions and clears it on resolve or timeout. Fixes #5286 by aligning feed.push with notification hooks and tightening workspace/panel targeting.

  • Bug Fixes
    • Drive attention in FeedCoordinator.ingestBlocking for PermissionRequest, ExitPlanMode, and AskUserQuestion: set .needsInput, show localized “Needs input”, ring the bell, and optionally reorder; inactive-app banner remains.
    • Clear on resolve or timeout with refcounted, race-free logic: record an AttentionTarget on the waiter inside ingest main.sync, conclude exactly once in reply/timeout, and don’t remove the shared workspace badge if another panel under the same status key still pends.
    • Resolve the target accurately and off-main: pre-resolve before main.sync, prefer live workspace_id, fall back to the session store only when it matches the resolved workspace, map surface → owning panel, DEBUG-log when a workspace can’t be resolved, and map claude → claude_code; add feed.status.needsInput and tests for the blocking path, decision predicate, and lifecycle key mapping.
    • Convert attention tests to Swift Testing and refresh the file-length budget.

Written for commit 0443082. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • In-app "Needs input" attention indicator and transient overlay that surfaces pending actions; can reorder tabs and request system attention when enabled.
  • Localization

    • Added English ("Needs input") and Japanese ("入力待ち") translations for the new status label.
  • Tests

    • Added automated tests to validate attention-surfacing behavior and to ensure test observers are cleared between runs.

austinywang and others added 2 commits June 3, 2026 14:14
…ention

Adds a regression test asserting that FeedCoordinator.ingestBlocking, for a
blocking PermissionRequest decision routed through `cmux hooks feed`, requests
in-app attention surfacing (needs-input status + bell + elevation). This fails
today because the feed decision bridge only ingests the card and posts an
inactive-app banner — it never drives the central attention path that the
`cmux hooks claude notification` hook uses (#5286).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When Claude Code (or any agent) blocks mid-turn on a PermissionRequest,
ExitPlanMode, or AskUserQuestion, the hook fires `cmux hooks feed` which sends
a blocking `feed.push`. The app ingested the Feed card and (when inactive)
posted a banner, but never drove the in-app attention path that the
`cmux hooks <agent> notification` hook uses — so there was no sidebar
"Needs input" status, no bell, and no tab/workspace elevation while the app
was active. The PreToolUse→PermissionRequest migration left this convergence
behind.

Fix it at the single choke point every feed-routed blocking decision flows
through: FeedCoordinator.ingestBlocking now calls
surfaceBlockingDecisionAttention for any blocking-decision event, which
- flips the owning workspace's agent lifecycle to .needsInput and sets the
  localized "Needs input" sidebar status (source→key mapped: claude→claude_code
  so the existing per-agent resume hooks clear it),
- elevates the workspace when Reorder on Notification is enabled,
- rings the bell.

This fires regardless of app focus; the existing inactive-app banner (with its
inline allow/deny actions) remains the desktop surface. Driving it once, for
every blocking-decision event, eliminates the class where a new feed event type
silently swallows attention. Fixes #5286.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 10, 2026 12:19am
cmux-staging Building Building Preview, Comment Jun 10, 2026 12:19am

@coderabbitai

coderabbitai Bot commented Jun 3, 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

Surfaces in-app “needs input” attention for blocking feed events: tracks refcounts per target, sets workspace lifecycle to .needsInput, writes a sidebar status entry, optionally reorders the tab, requests user attention, adds a localized string, and includes tests and a DEBUG hook.

Changes

Blocking decision attention surfacing

Layer / File(s) Summary
Localization strings for needs-input status
Resources/Localizable.xcstrings
Adds the feed.status.needsInput localization key with English ("Needs input") and Japanese ("入力待ち") translations.
Coordinator imports and attention model
Sources/Feed/FeedCoordinator.swift
Imports Bonsplit, introduces pendingAttentionCounts, AttentionTarget, needsInputStatusValue, and the DEBUG FeedCoordinatorTestHooks.attentionSurfaceObserver.
Ingest wiring and waiter persistence
Sources/Feed/FeedCoordinator.swift
Calls surfaceBlockingDecisionAttention(event:) during ingestBlocking, stores the resolved attentionTarget on the PendingWaiter, handles timeouts via concludeAttentionOnMain, and extends PendingWaiter with attentionTarget.
Surface, resolution, and conclusion logic
Sources/Feed/FeedCoordinator.swift
Implements surfaceBlockingDecisionAttention(event:), concludeBlockingDecisionAttention(_:), isBlockingDecisionEvent, lifecycleStatusKey(forSource:), and target-resolution helpers that set lifecycle, update sidebar status, optionally move the tab, refcount overlays, and clear them on conclusion.
Tests and recorder
cmuxTests/FeedCoordinatorTests.swift
Adds testBlockingIngestSurfacesNeedsInputAttentionForPermissionRequest, installs/clears FeedCoordinatorTestHooks.attentionSurfaceObserver in teardown, and adds AttentionSurfaceRecorder for thread-safe capture and assertions.

Sequence Diagram

sequenceDiagram
  participant FeedCoordinator
  participant WorkstreamEvent
  participant Workspace
  participant Sidebar
  participant TabManager
  participant System

  FeedCoordinator->>WorkstreamEvent: ingestBlocking(event)
  FeedCoordinator->>FeedCoordinator: surfaceBlockingDecisionAttention(event)
  FeedCoordinator->>Workspace: set lifecycle to .needsInput (store previous)
  FeedCoordinator->>Sidebar: write status entry "Needs input"
  FeedCoordinator->>TabManager: optionally move workspace tab to top
  FeedCoordinator->>System: request user attention (NSApp.requestUserAttention)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • manaflow-ai/cmux#5286: Describes missing sidebar/bell/tab elevation for PermissionRequest feed events that this change addresses.

Possibly related PRs

  • manaflow-ai/cmux#3924: Modifies the same FeedCoordinator.ingestBlocking flow (gating/cancelling native permission notifications) and touches related test coverage.

Poem

🐰 I hopped through hooks and strings today,
A bell and sidebar showed the way,
When input waits, I softly peep,
The recorder notes the promise we keep,
Hooray—tabs, strings, and tests all play.


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Algorithmic Complexity ❌ Error PR introduces O(n) linear scans on store.items (up to 2000 items) in FeedCoordinator.swift:237-255 and FeedPanelView.swift:982-990, called on user-interaction hot paths without indexed lookup. Replace store.items.first(where:) scans with a requestId↔itemId dictionary index maintained by WorkstreamStore for O(1) lookup instead of O(n).
Cmux Full Internationalization ❌ Error New feed.status.needsInput in Localizable.xcstrings has only 2 locale translations (en, ja) but the catalog supports 20; violates rule requiring new keys to have entries for all supported locales. Add translations for all 18 additional locales (ar, bs, da, de, es, fr, it, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, zh-Hant) to match other feed.* entries or document feed localization scope separately.
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (15 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: fixing issue #5286 by surfacing needs-input attention for feed-routed blocking decisions.
Linked Issues check ✅ Passed Code changes fully address #5286 objectives: surface in-app attention (sidebar status, bell, workspace elevation) for PermissionRequest and other feed-routed blocking events, preserve inactive-app banner, add tests and localization.
Out of Scope Changes check ✅ Passed All changes directly support the stated objectives: localization strings, FeedCoordinator attention logic, test coverage, and test helpers are all within scope of fixing the silent-swallow bug.
Cmux Swift Actor Isolation ✅ Passed All new code properly isolated: @MainActor methods guard UI state access; pure static helpers avoid isolation; AttentionTarget is Sendable; test helpers use NSLock for thread safety.
Cmux Swift Blocking Runtime ✅ Passed No new blocking/timing synchronization in production code. Existing DispatchSemaphore.wait pre-dated this PR. NSLock only used in test-only AttentionSurfaceRecorder helper.
Cmux No Hacky Sleeps ✅ Passed PR changes are in Swift and localization files only. Check scope excludes Swift (covered by separate check) and applies only to TypeScript, JavaScript, shell, and build/runtime scripts.
Cmux Swift Concurrency ✅ Passed All Task patterns are created within OS/third-party callback boundaries (DispatchSource, UNUserNotificationCenter), which are explicitly allowed per the modernization rules.
Cmux Swift @Concurrent ✅ Passed All new Swift methods follow concurrent annotation rules: @MainActor functions correctly isolate UI work, static helpers are synchronous, no missing @concurrent, no invalid @concurrent usage.
Cmux Swift File And Package Boundaries ✅ Passed Adds 246 lines to oversized FeedCoordinator (917 lines), within 250-line limit. New code is AppKit/UI glue with AppDelegate and TabManager dependencies—allowed as app-target UI orchestration.
Cmux Swift Logging ✅ Passed FeedCoordinator.swift contains only one DEBUG-guarded cmuxDebugLog call (line 356), which is allowed per rules. No print/debugPrint/dump/NSLog in production code, no file logging, no secrets exposed.
Cmux User-Facing Error Privacy ✅ Passed User-facing strings are generic ("Needs input"), contain no vendor names, credentials, or implementation details. Debug logging is behind #if DEBUG. No sensitive data exposed in production UI.
Cmux Swiftui State Layout ✅ Passed PR introduces no SwiftUI View changes or state patterns; changes are confined to non-SwiftUI FeedCoordinator class, localization file, and tests.
Cmux Architecture Rethink ✅ Passed Fills missing lifecycle for blocking feed decisions; tab owns state; coordinator calls existing APIs; no new owners, timing repairs, or duplicate wiring
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No NSWindow, NSPanel, NSWindowController, SwiftUI Window/WindowGroup created. Changes only add localization and manage existing Tab state.
Description check ✅ Passed PR description is comprehensive with clear problem statement, root cause analysis, fix explanation, test coverage, and localization details. All required sections from the template are present and well-populated.
✨ 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 issue-5286-feed-permissionrequest

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 and usage tips.

Comment thread Sources/Feed/FeedCoordinator.swift
Comment thread Sources/Feed/FeedCoordinator.swift Outdated
@greptile-apps

greptile-apps Bot commented Jun 3, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes #5286 by converging all feed-routed blocking decisions (PermissionRequest, ExitPlanMode, AskUserQuestion) through a single attention path in FeedCoordinator.ingestBlocking, driving .needsInput lifecycle, "Needs input" sidebar status, optional workspace reorder, and dock bell — regardless of app focus. The existing inactive-app banner with inline allow/deny actions is preserved.

  • Attention surface: surfaceBlockingDecisionAttention runs on the main actor; target resolution (including FeedJumpResolver disk I/O) is pre-computed off-main before entering DispatchQueue.main.sync, keeping the UI critical section to pure AppKit mutations.
  • Race-free cleanup: AttentionTarget is stored on the waiter inside the ingest main.sync block (before the card can render) and refcounted in pendingAttentionStates; concludeBlockingDecisionAttention guards each cleanup step with value checks so an agent hook that updates lifecycle/status first always wins.
  • Localization: feed.status.needsInput is added with all 20 catalog locales. Tests cover the full blocking-ingest path, the decision predicate, and the claude→claude_code lifecycle key mapping.

Confidence Score: 5/5

Safe to merge; attention surfacing is well-scoped to FeedCoordinator with correct refcounting, race-free target recording, and guarded cleanup.

The convergence logic in ingestBlocking is carefully sequenced: disk I/O is moved off the main.sync critical section, the AttentionTarget is stored on the waiter inside main.sync before any reply can fire, and concludeBlockingDecisionAttention guards every mutation with a value check so agent hooks always win. Double-conclude is prevented by the pendingAttentionStates guard. Localization covers all 20 catalog locales.

Sources/Feed/FeedCoordinator.swift — grew significantly; the attention-surfacing extension could be split into its own file if the team wants to keep file sizes in check.

Important Files Changed

Filename Overview
Sources/Feed/FeedCoordinator.swift Adds surfaceBlockingDecisionAttention / concludeBlockingDecisionAttention with refcounted AttentionTarget overlay logic; pre-resolves disk I/O before main.sync; correctly stores target inside ingest main.sync to eliminate deliverReply race. File grew 944→1239 lines (31% increase, already oversized).
cmuxTests/FeedCoordinatorTests.swift Adds blockingIngestSurfacesNeedsInputAttentionForPermissionRequest, blockingDecisionEventPredicateCoversEveryDecisionKind, lifecycleStatusKeyMatchesAgentReportedKey tests; adds attentionSurfaceObserver reset to shared teardown; AttentionSurfaceRecorder is thread-safe via NSLock.
Resources/Localizable.xcstrings Adds feed.status.needsInput with all 20 supported locale entries (ar, bs, da, de, en, es, fr, it, ja, km, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, zh-Hant) as translated strings.
.github/swift-file-length-budget.tsv Budget bumped for FeedCoordinator.swift (944→1239) and FeedCoordinatorTests.swift (575→668) to match actual file growth.

Sequence Diagram

sequenceDiagram
    participant Hook as CLI Hook (socket thread)
    participant FC as FeedCoordinator
    participant Main as Main Thread
    participant UI as Workspace / Sidebar

    Hook->>FC: ingestBlocking(event, timeout)
    FC->>FC: resolveAttentionTarget() [disk I/O, off-main]
    FC->>Main: DispatchQueue.main.sync
    Main->>UI: store.ingest(event)
    Main->>UI: surfaceBlockingDecisionAttention()
    UI-->>UI: setAgentLifecycle(.needsInput)
    UI-->>UI: statusEntries[Needs input]
    UI-->>UI: moveTabToTopForNotification (if enabled)
    UI-->>UI: NSApp.requestUserAttention
    Main->>FC: "waiters[requestId].attentionTarget = target"
    Main-->>Hook: main.sync returns
    Hook->>Hook: semaphore.wait(timeout)

    alt User decides (deliverReply)
        Main->>FC: deliverReply(requestId, decision)
        FC->>FC: signal semaphore + read attentionTarget
        FC->>Main: concludeAttentionOnMain(target) async
        Main->>UI: concludeBlockingDecisionAttention()
        UI-->>UI: reset lifecycle if still .needsInput
        UI-->>UI: remove status entry if still Needs input
        Hook-->>Hook: semaphore.wait success resolved
    else Timeout
        Hook-->>Hook: semaphore.wait timedOut
        FC->>Main: concludeAttentionOnMain(target) async
        Main->>UI: concludeBlockingDecisionAttention()
    end
Loading

Reviews (10): Last reviewed commit: "Convert merged attention tests to Swift ..." | Re-trigger Greptile

Comment thread Resources/Localizable.xcstrings Outdated
Comment thread Sources/Feed/FeedCoordinator.swift

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 3 files

Re-trigger cubic

…ce via session store

- Make the needs-input overlay fully transient: capture the prior agent
  lifecycle + sidebar status when surfacing a blocking decision and restore it
  when the decision concludes (resolved via deliverReply, or timed out in
  ingestBlocking). Previously the lifecycle/status were never cleared on
  timeout, leaving a stale "Needs input" badge (cursor[bot]). The overlay
  context rides on the existing PendingWaiter (no new lock).
- Resolve the owning workspace from the agent's hook-session store as a
  fallback when the event omits workspace_id, instead of exiting early
  (cursor[bot]).
- Map the session-store surface id to its owning panel id.
- Add unit tests for the blocking-decision predicate and the source→status-key
  mapping (documents the claude→claude_code special case, greptile).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread Sources/Feed/FeedCoordinator.swift Outdated
Comment thread Sources/Feed/FeedCoordinator.swift Outdated

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 2 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Sources/Feed/FeedCoordinator.swift Outdated
The snapshot/restore approach (capture prior lifecycle on surface, restore on
conclusion) had two concurrency hazards the bots correctly flagged:
- The waiter context was stored after main.sync returned, racing deliverReply
  (cursor/greptile P1): a fast reply could read a nil context, skip restore,
  and the .resolved path returned assuming restore already ran → stuck badge.
- Restoring a captured snapshot unconditionally clobbered a newer overlapping
  decision's needs-input on the same panel (cubic P2).

Remove the snapshot/restore machinery entirely (BlockingAttentionContext, the
waiter field, restore helpers, the context slot). surfaceBlockingDecisionAttention
now only *sets* the needs-input overlay, mirroring the existing
`cmux hooks <agent> notification` path. Clearing back to running/idle is owned by
the agent's own lifecycle hooks under the same statusKey (Claude's pre-tool-use
on resume, notification re-asserting after a timed-out TUI fallback, stop on turn
end). Single owner for lifecycle transitions = no race, no clobber.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 3, 2026

@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

🤖 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 `@Sources/Feed/FeedCoordinator.swift`:
- Around line 299-302: When resolveAttentionTarget(event:) returns nil the
function silently returns; update FeedCoordinator to capture that failure by
adding a debug log or telemetry breadcrumb before returning. Specifically, in
the guard that uses Self.resolveAttentionTarget(event: event), detect the nil
case and emit a telemetry/log entry containing the event.sessionId and
event.hookEventName (and any other useful context like event.id or timestamp),
then return as before; reference the existing guard and the
resolveAttentionTarget(event:) symbol so the change is localized and
non-invasive.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 0b54cf61-c7d2-4884-930b-2bc59f96eb6b

📥 Commits

Reviewing files that changed from the base of the PR and between 31f4bcb and e2953ad.

📒 Files selected for processing (1)
  • Sources/Feed/FeedCoordinator.swift

Comment thread Sources/Feed/FeedCoordinator.swift Outdated
…kspace

Add a DEBUG breadcrumb when surfaceBlockingDecisionAttention can't resolve an
owning workspace and returns early. This PR is about silent swallowing, so
making the one remaining silent return observable (session id + hook event +
workspace) helps diagnose field cases where a blocking decision fails to
surface. Wrapped in #if DEBUG per the debug-log policy (coderabbitai).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread Sources/Feed/FeedCoordinator.swift
Comment thread Sources/Feed/FeedCoordinator.swift
Two follow-up findings (cursor): (1) needs-input could stay stuck after an
approval for agents without a resume hook (e.g. Codex), since the feed relied on
agent hooks to clear it; (2) a stale hook-session map could win over the live
workspace_id and target the wrong workspace.

Reintroduce a feed-side clear, but correct this time:
- surfaceBlockingDecisionAttention returns an AttentionTarget and the target is
  stored on the waiter INSIDE the ingest main.sync (before the card renders /
  a reply can fire), closing the race the prior attempt had.
- Clearing is refcounted per (workspace, panel, statusKey): overlapping
  decisions keep the badge lit until the last concludes — no clobber.
- concludeBlockingDecisionAttention is check-and-clear: it only sets lifecycle
  back to .running if it's still .needsInput, and removes the status entry only
  if it still holds our "Needs input" value, so a real agent-hook update always
  wins.
- deliverReply concludes on resolve; the ingestBlocking timeout branches
  conclude on timeout — exactly once per surfaced decision.
- resolveAttentionTarget now prefers the live event workspace_id over the
  session store, and only trusts the session surface when its workspace matches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Sources/Feed/FeedCoordinator.swift Outdated
…ends

The sidebar status entry is workspace-level (keyed only by statusKey), so two
panels running the same agent share it. The per-(workspace, panel, statusKey)
refcount protected the per-panel lifecycle but not the shared status entry:
concluding one panel's decision could wipe another panel's active "Needs input"
badge (cubic). Guard the status removal on no other panel in the same workspace
having a pending decision under the same key. Lifecycle clearing stays per-panel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7e9a0eb. Configure here.

Comment thread Sources/Feed/FeedCoordinator.swift Outdated
Comment thread Sources/Feed/FeedCoordinator.swift Outdated
@austinywang

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 4, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@austinywang
austinywang dismissed coderabbitai[bot]’s stale review June 4, 2026 12:26

Stale CodeRabbit change request from e2953ad. The requested debug logging for unresolved feed attention targets was implemented in b7decdd, all review threads are resolved, and the current CodeRabbit check is green on 5988f89.

Main migrated FeedCoordinatorTests to Swift Testing while this branch
added XCTest-style tests; the merge left XCTest symbols out of scope.
Match the suite's semaphore + #expect house pattern and accept the
FeedCoordinator growth in the file-length budget.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lawrencecchen
lawrencecchen merged commit daeca99 into main Jun 10, 2026
20 checks passed
@lawrencecchen
lawrencecchen deleted the issue-5286-feed-permissionrequest branch June 10, 2026 00:45
austinywang added a commit that referenced this pull request Jun 10, 2026
After the PreToolUse->PermissionRequest migration (#5313), Claude Code's
blocking decisions surface "Needs input" through the feed path
(FeedCoordinator.surfaceBlockingDecisionAttention), which sets the sidebar
status and per-panel needsInput lifecycle WITHOUT creating a store
notification. The notification-store acknowledgement added for #2576 can
never reach that state, so clicking into the workspace left the badge
stuck on "Needs input".

Add FeedCoordinator.acknowledgeBlockingDecisionAttention(workspaceId:panelId:)
— the feed-path analogue of the store mark-read acknowledgement — and call
it from TabManager.dismissNotification, the single shared focus/interaction
seam every entrypoint (sidebar resume, panel click, terminal interaction)
funnels through. Targets are force-concluded regardless of refcount and
scoped to the focused panel so sibling panels keep their badge; the
decision's later concludeBlockingDecisionAttention becomes a safe no-op.
Overlay teardown is factored into a shared clearResolvedBlockingDecisionOverlay
helper reused by conclude and acknowledge.

Fixes #2576

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@lawrencecchen
lawrencecchen restored the issue-5286-feed-permissionrequest branch July 18, 2026 10:19

This branch was successfully deployed

1 active deployment
Preview – cmux — 04430821 Deployed Jun 10, 2026 by vercel[bot]
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.

PermissionRequest hook (cmux hooks feed) silently swallows events — no sidebar status, no bell, no tab elevation

2 participants