Skip to content

Approvals render in the workbench thread again, and a parked worker says it needs you - #1118

Merged
pratikbuilds merged 8 commits into
corbitsdev:mainfrom
pratikbuilds:cl-1117-inline-approvals
Oct 17, 2026
Merged

pratikbuilds merged 8 commits into
corbitsdev:mainfrom
pratikbuilds:cl-1117-inline-approvals

Conversation

@pratikbuilds

Copy link
Copy Markdown
Collaborator

Summary

Closes #1117. A worker parked on an approval looked like it was still working: the thread showed nothing, the pill said "working", and the drawer's Needs you row had no buttons. Approvals now render in the thread where the decision happens, as DESIGN.md describes.

 <Workbench> (apps/web/src/pages/workbench-page.tsx)
   <BenchPill>
-    "<worker> is working …"
+    "<worker> needs you"            while a live ask is pending
   <chat-thread-messages>
     <WorkbenchMessageRow>…
     <PendingOpeningMessage>
+    <ThreadApprovals>               (bench/thread-approvals.tsx)
+      <ApproveBlockView>            existing card: Allow once / Always allow / Ask every time / Deny
     <WorkingLabel>
   <BenchDrawer> → Information → Needs you
-    row + static "Waiting" pill
+    <ApprovalRow>                   the call it gates + Approve / Deny (bench/approval-row.tsx)

The thread only shows asks raised by a live agent address. An ask whose run is gone (e.g. after a hub restart) can't be answered, so it stays in the drawer only, where a 409 now reads "already resolved or its worker stopped" instead of "The server answered 409."

A parked run sends no mail and ticks no inbox event, so nothing refetched the approvals list. Polling now follows the pending turn:

 useBenchWorkerStatus
   approvals query
+    refetchInterval: pending turn ? 5s : off     (shared by every approvals surface)

 answer an approval (thread card or drawer)
+  drop the answered row from the cache
+  markTurnPending(bench)                        the run resumes and may park again
   invalidate approvals

Dropping the row first matters: with the stale row still cached, the resumed turn settled immediately and the next ask in the same reply never surfaced.

Evidence

e2e/browser/approval.test.ts, against scripted inference (aimock) on an isolated database:

thread: worker calls run_shell twice in one turn
  1st ask   → full command shown in the thread, pill says "needs you"
  Allow once → 2nd ask surfaces in the thread without reload
  Allow once → worker's reply lands, no card left

drawer: worker calls run_shell once
  Information → Needs you shows the command → Approve → reply lands
  → "Nothing is waiting on you."
  • Before: the thread test fails waiting for the command in the thread. With the cache drop removed, it fails waiting for the 2nd ask.
    After: 2 pass, 0 fail.

Typecheck, lint, fmt and the apps/web unit tests (186) pass. bun run check fails at the test step in five packages this PR doesn't touch (approvals, deferred-tools, url-path, workflows, worker): they have no test files, and bun test exits non-zero when it finds none.

Merge Danger

Door: two-way

UI and e2e only. No schema, API, or data changes.

Blast Radius: workbench-thread

Approvals polling runs every 5s per bench only while a turn is pending, as the timeline poll already does. createChatApprovalActions had no callers before this PR.


Notes for review:

  • The new test has its own signup → bench helper and waits for an enabled composer before typing. The open PR with the shared startWorkbench helper in e2e/lib/first-run.ts covers the same race; this test can switch to it once that lands.
  • e2e/lib/hub.ts boots the browser-suite hub on the raw DATABASE_URL, i.e. the dev database from .env. Running these suites beside bun run dev puts two hubs on one database, and the dev hub picks up the suite's sidecar allocations. That's why the worker sometimes never launched in these suites. Worth its own issue.

The Needs you section listed a parked ask with a static Waiting pill and
no way to resolve it, so the only remaining approval surface could not
unpark the run. The row now shows the call it is gating and Approve /
Deny, and a 409 says the ask can no longer be answered instead of
echoing the status code.
A run that parks on an approval sends no mail and ticks no inbox event,
so the approvals list stayed stale and the worker kept reading as
working while it was blocked on the person.
Approvals render in the thread where the decision happens, with Allow
once, Always allow, Ask every time and Deny. Only asks raised by a live
agent address are shown, since an ask whose run is gone can no longer
be answered.
Answering an ask resumes the run, which can park again on its next tool
call. The turn is marked pending again so the next ask surfaces, and the
answered row is dropped from the cache first, since the stale list would
otherwise settle the resumed turn before its refetch lands.
@pratikbuilds
pratikbuilds marked this pull request as ready for review October 13, 2026 13:04
An ask left pending by a run that is gone can't be answered, so it must not
settle the live worker's pending turn; the worker's own ask still does.
A pending ask from a run that is gone settled every new turn at once, which
stopped the approvals poll, so the live worker's next ask never surfaced and
the pill read Live. The turn now settles only on the live worker's own ask,
the same check the Needs you status uses.
@pratikbuilds
pratikbuilds merged commit 848fad9 into corbitsdev:main Oct 17, 2026
6 checks passed
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.

Regression: pending approvals no longer appear in the workbench thread, and nothing signals a parked worker

2 participants