Skip to content

Show hidden threads that are waiting on the user - #4408

Closed
MacHatter1 wants to merge 1 commit into
get-bb:mainfrom
MacHatter1:feat/hidden-thread-attention-overlay
Closed

MacHatter1 wants to merge 1 commit into
get-bb:mainfrom
MacHatter1:feat/hidden-thread-attention-overlay

Conversation

@MacHatter1

Copy link
Copy Markdown
Contributor

Human comments

What was wrong

Hidden threads are left out of the sidebar and out of pending attention, and when a thread blocks on a person, the only notification goes to its parent (requestChildThreadNeedsAttentionNotification returns early unless parentThreadId is set). A hidden root thread, which is what every Workflows worker is and what --visibility hidden creates, has no parent. So when one stopped for an approval or a question, nothing in the app pointed at it, and it waited until someone opened it by ID or the run timed out. The repro, real-world cases and code links are in #4407.

What changed

The fix surfaces waiting interactions on hidden threads without changing hidden-thread visibility, the sidebar or notifications.

Before and after: on main the sidebar shows No threads while a hidden thread is blocked; with this change a "1 background thread needs you" tray shows the approval, answerable in place

  • Tray (app): BackgroundAttentionTray, mounted app-wide in AppLayout. While any hidden thread is waiting, a "N background threads need you" pill sits at the bottom right of every page. It opens a "Waiting on you" popover that lists the existing ThreadPendingInteractionBanners, so every interaction kind (approvals, questions, plan reviews, plugin requests) is answerable in place. Each item links to its thread, titled "thread · owner". The thread that's already open is skipped, since its own banner shows. The popover uses the shared responsive Popover, so compact viewports get the drawer. ThreadPendingInteractionBanner gains an initiallyExpanded prop, defaulting to false, so thread views are unchanged. The favicon dot also lights while something is waiting (shouldShowFaviconAttentionDot gains backgroundThreadNeedsAttention).
  • Data: a new read-only route, GET /api/v1/threads/pending-interactions. It lists active (pending/resolving) interactions across threads, newest first, and leaves out archived and deleted threads.
    • visibility: any (the default), hidden or visible.
    • limit: 1–200, default 50.
    • Each entry carries the interaction, a thread summary and the owner: the parent, else the lifecycle owner.
    • The code: listActivePendingInteractionAttention in @bb/db, listPendingInteractionAttention on the pending-interaction lifecycle, and the route and schemas in @bb/server-contract.
    • There's no migration and no new broadcast. The query refreshes on the existing interactions-changed thread-list broadcast, and on thread archive and delete, through the cache registry and invalidation groups.
  • SDK and CLI: bb.sdk.threads.experimental_listPendingInteractions({ visibility, limit }), with an entry in docs/api_to_audit.md. bb thread interactions pending [--visibility any|hidden|visible] [--limit <n>] [--json] is documented in bb-guide-threads.md, bb-guide-json.md and the bb-cli command index.
  • Bundle: the tray's list is a lazy chunk, preloaded once something is waiting, so the boot payload grows by 4.3 KB (1457.9 to 1462.2 KB raw). An eager import had added 553 KB.
    • Because the lazy chunk shares ThreadPendingInteractionBanner with the thread route, the bundler moves the banner and three helpers out of the route's largest chunk into shared chunks. The SplitWorkspaceRoute closure grows by 2.0 KB raw and 5.1 KB brotli, from chunk overhead rather than new route code.
    • bundle-budget.json raises those limits by 2.5 KiB raw and 4.5 KiB brotli, with a dated note. The raw rise also covers the 0.3 KB that main is already over at a19b3ed, which currently fails CI on main.
    • A lighter copy of the banner would avoid the split, but it would duplicate every interaction kind's UI.

No HOST_DAEMON_PROTOCOL_VERSION change and no provider changes. Hidden threads stay hidden.

More screenshots

The pill, on a page where the sidebar shows no threads:

The pill reads 1 background thread needs you

On a phone (390×844), the tray opens as a bottom drawer:

The tray as a bottom drawer on a phone

After Allow once, the hidden thread carries on and the pill goes away:

The pill is gone after the approval is answered

How you verified

New tests (the route and the tray are new, so they fail on main):

  • apps/server/test/public/public-thread-pending-interaction-attention.test.ts (5 tests). The response is parsed with the contract schema. The tests cover:
    • a hidden worker's approval, returned with its thread and lifecycle owner;
    • the hidden, visible, any and default visibility filters;
    • the parent taking precedence over the lifecycle owner, and a root hidden thread having no owner;
    • settled interactions, archived threads and a worker archived with its owner all being left out;
    • newest-first ordering and limit, with 0 and 201 rejected with a 400.
  • apps/app/src/components/notifications/BackgroundAttentionTray.test.tsx (7 tests):
    • nothing renders while nothing is waiting;
    • a hidden thread's approval shows expanded, is answered with that thread's ID, and links to the thread;
    • the open thread is skipped, and the tray hides when that thread is the only one waiting;
    • the label pluralises, source titles fall back correctly, and filtering works.
  • faviconAttentionDot.test.ts: 2 new cases, for the dot on and off with a hidden thread waiting.
  • Updated: cache-owner-registry.test.ts declares the new query key for both cache owners, three AppLayout test files mock the tray, and the SDK public-types test lists the new method.

Commands, on this branch:

  • pnpm exec turbo run build typecheck lint (CI's check step): 188/188 tasks passed. Lint reports 0 errors, and none of the existing @bb/app warnings are in changed files.

  • node apps/app/scripts/check-bundle-budget.mjs passes:

    main at a19b3ed This branch Limit
    Boot payload (raw / brotli) 1457.9 / 360.9 KB 1462.2 / 361.8 KB 1675.2 / 413.2 KB (unchanged)
    SplitWorkspaceRoute closure (raw / brotli) 2286.0 / 614.9 KB 2288.0 / 620.0 KB raised to 2288.3 / 620.3 KB
  • node scripts/check-provider-literal-ratchet.mjs and node scripts/check-source-nul.mjs both pass.

  • pnpm exec turbo run test:

    • @bb/app: 5148 passed and 5 skipped, across 561 files.
    • @bb/db 584, @bb/server-contract 78, @bb/sdk 113 and @bb/cli 862 passed.
    • @bb/templates: all passed. The external scaffold test needs NPM_CONFIG_USERCONFIG pointed at an empty file on this machine, because my ~/.npmrc sets allow-scripts, which npm 11 rejects for project-scoped installs.
    • @bb/server: 3270 passed and 2 failed. Both failures are 5 s timeouts in test/app/install-machine-script.test.ts under full-suite load. That file passes 48/48 on its own, and it fails the same way on main here.

Manual, in an isolated pnpm dev instance with its own data directory and ports:

  • Before, on main at a19b3ed: a hidden Claude Code thread in accept-edits, asked to run curl -sI https://example.com, blocked on "Allow network connection to example.com?". Nothing in the app pointed at it (screenshot above).
  • Allow once, on this branch, same setup:
    • With the sidebar still showing "No threads", the pill appeared. The tray's list chunk was fetched before the click, so the preload works.
    • The tray showed the approval expanded. Allow once granted the permission, the thread ran the command and went idle, and the pill went away.
    • The favicon changed when the approval was answered.
  • Deny: on a second hidden thread, from the 390×844 drawer, Deny reached the thread as a denial, the thread finished, and the pill went away.
  • CLI: while each approval was waiting, bb thread interactions pending --visibility hidden listed it, marked (hidden). Afterwards it printed "No interactions are waiting". bb thread list --project <proj> showed no threads while the approval waited.

Fixes #4407

A hidden thread stays out of the sidebar and out of unread/pending
attention, and only a thread with a parent notifies anyone when it
blocks. So a hidden root thread waiting on an approval or question
(a Workflows worker, for instance) stalls with nothing in the app
pointing at it, sometimes for the whole run timeout.

The app now shows an app-wide tray for pending interactions on hidden
threads: a "N background threads need you" pill at the bottom right
that opens the existing pending-interaction banners, expanded, each
answerable in place and linking to its thread with the owner's title.
It skips the open thread, lights the favicon dot, and uses the shared
responsive popover, so compact viewports get the drawer.

The tray's list is a lazy chunk, preloaded once something is waiting,
so the boot payload grows by about 4 KB. The chunk shares the banner
with the thread route, which splits the banner out of the route's
largest chunk, so the SplitWorkspaceRoute budget rises 2.5 KiB raw and
4.5 KiB brotli.

The data comes from a new GET /threads/pending-interactions route that
lists active interactions across threads (visibility any, hidden or
visible; newest first; limit 1-200) with their thread and owner (the
parent, else the lifecycle owner). The existing interactions-changed
broadcast already reaches every client, so the tray refreshes on it,
on archive and on delete.

It is also available as bb.sdk.threads.experimental_listPendingInteractions
and `bb thread interactions pending`, with the guide, JSON guide, command
index and api_to_audit entries updated.
@SawyerHood SawyerHood closed this Sep 29, 2026
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.

A hidden thread waiting on an approval or question stalls with nothing in the app pointing at it

2 participants