Skip to content

Surface pending interactions in the CLI and add thread wait --until-input - #1698

Open
SawyerHood wants to merge 4 commits into
mainfrom
bb/investigate-1655-thr_4sgani5hgi
Open

Surface pending interactions in the CLI and add thread wait --until-input#1698
SawyerHood wants to merge 4 commits into
mainfrom
bb/investigate-1655-thr_4sgani5hgi

Conversation

@SawyerHood

Copy link
Copy Markdown
Collaborator

Fixes #1655

Problem

When a provider agent raises a question, approval, or permission request, the thread stays active but makes no progress. bb thread tell and bb thread queue send return HTTP 409 awaiting_user_interaction, and no CLI surface shows the pending card, so a coordinator sees a silent stall.

bb thread interactions list|show|answer|approve|grant|deny already resolves interactions, but the guide and skill did not document it, the 409 message did not name it, and bb thread show never mentioned the pending card.

Changes

  • bb thread show: the status line reads active (waiting for input) and a Pending interactions (N): section lists each interaction with its id, kind, summary, question options, and the exact resolve command. --json includes pendingInteractions.
  • bb thread list: threads with hasPendingInteraction: true show waiting for input in the status column.
  • bb thread wait --until-input (SDK threads.wait({ untilInput: true })): returns the pending interaction with exit code 0, and exits with the unreachable code (4) when the thread is no longer active, because an idle or errored thread cannot ask for input.
  • bb thread log: pending question rows print the option values and Answer with: bb thread interactions answer <id> <thread>; pending permission and file-edit approval rows print the matching grant/approve command.
  • The 409 message in thread-send.ts now names bb thread interactions list <id> and bb thread stop <id>. The send route queueing behavior is unchanged (owned by Messages to a thread blocked on AskUserQuestion are dropped, and only the sender is told #1650).
  • bb guide threads and the bb-cli skill document the Interactions commands and the coordinator recipe.
  • Regenerated @bb/templates and the plugin-sdk bundled types.

Test plan

  • @bb/sdk: untilInput polls thread + interactions and returns the pending one; throws ThreadWaitUnreachableError on an idle thread.
  • @bb/cli: thread show pending block and JSON field; thread list flag; thread wait --until-input text, JSON, and flag conflicts.
  • @bb/thread-view: snapshot for pending question and permission hints.
  • @bb/server: 409 message tests updated.
  • pnpm exec turbo run typecheck test --filter=@bb/cli --filter=@bb/sdk --filter=@bb/thread-view --filter=@bb/templates --filter=@bb/server (one unrelated umask-dependent server test, internal-skill-trees, fails locally on this machine).

AGENT GENERATED: by Claude Opus 5

SawyerHood and others added 3 commits August 17, 2026 17:21
…nput

Threads that wait for a question, approval, or permission grant looked
healthy from the CLI and rejected new prompts with HTTP 409. This change
makes the stall visible and resolvable from the CLI and SDK:

- bb thread show prints 'active (waiting for input)' plus a Pending
  interactions section with the exact resolve command; --json includes
  pendingInteractions.
- bb thread list marks such threads with 'waiting for input'.
- bb thread wait --until-input (SDK: untilInput) returns the pending
  interaction, and exits with the unreachable code when the thread went
  idle or errored first.
- bb thread log prints question options and the answer/grant/approve
  command under pending question and approval rows.
- The 409 awaiting_user_interaction message names the CLI commands that
  resolve or discard the pending interaction.
- The bb guide and bb-cli skill document bb thread interactions.

Fixes #1655

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@bb-slop-cop

bb-slop-cop Bot commented Aug 17, 2026

Copy link
Copy Markdown

🚨 SLOP COP 🚨 · review

I am the Slop Cop. I am reviewing this pull request for security, code quality, performance, architecture, duplication, and practical behavior.

return `Inspect it with 'bb thread interactions show ${interaction.id} ${interaction.threadId}'; plugin interactions can only be answered in the app.`;
}
const action = verb === "answer" ? "Answer" : verb === "grant" ? "Grant" : "Approve or deny";
return `${action} it with 'bb thread interactions ${verb} ${interaction.id} ${interaction.threadId}'.`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review — The generated grant command gives session access by default.

The hint omits --scope. parsePermissionGrantScope(undefined) selects session. A copied command can grant more access than one request needs.

Add --scope turn to generated hints. Another safe option is to require an explicit scope.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed and fixed in c4ccb7f: the CLI hint and the timeline hint now append --scope turn.

Comment thread packages/sdk/src/areas/threads.ts Outdated
while (true) {
if (target.kind === "status") {
if (target.kind !== "event") {
const thread = await getThread({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review — One input wait can send about 9,600 HTTP requests.

This loop sends two requests every 250 milliseconds. The default timeout is 20 minutes.

Add a server wait endpoint that uses interaction-change notifications. As a minimum, add backoff and avoid two requests each cycle.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed in part and fixed in c4ccb7f: the wait now polls the interaction list first and checks the thread status only every eighth cycle (about every 2s at the default interval), so a default wait sends about half the requests of before. A server-side wait endpoint for interaction changes is a larger change; I left it out of this PR.

: [];
}
return row.lifecycle === "waiting"
? [formatResolveHint("Approve", row, context)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review — The file-edit approval command uses the wrong identifier.

The file-edit row stores message.callId as interactionId. The resolve route requires a pint_* pending-interaction identifier.

Carry the actual interaction identifier through the timeline contract. Add a test where the call and interaction identifiers differ.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed and fixed in c4ccb7f: file-edit rows now print Approve or deny with: bb thread interactions list <thread>, which shows the real interaction id, instead of a command built from the tool call id.

): string {
const verb = resolveInteractionVerb(interaction);
if (verb === null) {
return `Inspect it with 'bb thread interactions show ${interaction.id} ${interaction.threadId}'; plugin interactions can only be answered in the app.`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review — The plugin hint points to a command that throws.

The hint recommends interactions show. That command treats each non-question payload as an approval, so it rejects plugin payloads.

Make show print plugin data. Update recovery text to name the app or a supported cancel command.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed and fixed in c4ccb7f: bb thread interactions show now prints plugin interactions (plugin id, title, data) and says to answer them in the app; a test covers it.

interaction,
surface: "cli",
});
console.log(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review — The new output permits terminal control characters.

This output writes provider and plugin text without control cleanup. An ESC or OSC string can change the terminal display or clipboard.

Remove C0, C1, ESC, and terminal control sequences from human output. Keep the original data only in JSON output.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed and fixed in c4ccb7f: the new bb thread show block strips C0/C1 controls and ESC/CSI/OSC sequences from provider and plugin text; --json keeps the raw data. A test feeds an OSC 52 clipboard sequence through a question prompt.

@bb-slop-cop bb-slop-cop 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.

🚨 SLOP COP 🚨 · review

Plain English summary: This PR helps CLI users see when an agent needs input. It also adds a wait command for automation.

I found five issues.

  1. High: Generated permission hints omit --scope. The copied command grants session access, not access for one turn.
  2. High: New human output permits terminal control characters from provider or plugin text. Such text can change the display or clipboard.
  3. Medium: One default --until-input wait can send about 9,600 HTTP requests. The SDK polls two endpoints every 250 milliseconds.
  4. Medium: File-edit timeline hints use an item call identifier. The approval route requires a pending-interaction identifier, so the command fails.
  5. Medium: Plugin hints recommend interactions show. That command rejects plugin payloads because it expects an approval payload.

The architecture scan found duplicate hint logic in the CLI and thread-view packages. A shared formatter would reduce the current scope and identifier differences.

Validation completed:

  • The CLI passed 455 tests.
  • The SDK passed 92 tests.
  • The thread-view package passed 369 tests.
  • The focused server interaction file passed 25 tests.
  • Six affected packages passed Turbo type checks.
  • The live source server returned a real plugin question through thread wait --until-input.
  • thread show and thread list displayed waiting for input during that live test.
  • GitHub CI passed all required checks.

The complete local server suite had one unrelated timeout. The focused changed server test passed after that result.

I used a comment review only. I did not approve the pull request or request changes.

- Grant hints add --scope turn so a copied command stays narrow.
- bb thread show strips terminal control sequences from provider text.
- Interaction waits poll the thread status only every eighth cycle.
- File-edit approval rows point at 'interactions list' because they carry
  the tool call id, not the pending-interaction id.
- bb thread interactions show prints plugin interactions instead of
  throwing.

Co-Authored-By: Claude <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.

Threads awaiting user interaction are invisible to CLI and block programmatic steering with HTTP 409

1 participant