Skip to content

fix(app): queue concurrent approval requests instead of overwriting them - #1159

Open
addyCooks wants to merge 1 commit into
Nano-Collective:mainfrom
addyCooks:fix/concurrent-approval-queue
Open

fix(app): queue concurrent approval requests instead of overwriting them#1159
addyCooks wants to merge 1 commit into
Nano-Collective:mainfrom
addyCooks:fix/concurrent-approval-queue

Conversation

@addyCooks

Copy link
Copy Markdown
Contributor

Closes #1156

Description

The three "ask the user" slots question-queue, tool-approval-queue and
tool-confirm-queue each stored a single resolver in a useRef. A second
caller arriving before the first was answered overwrote it, and that first
promise could then never settle.

tool-executor starts up to MAX_CONCURRENT_AGENTS (5) subagents in one turn
and awaits them with Promise.allSettled, so one stranded caller meant the batch
never resolved and the turn never ended. Escape did not help either: the subagent
was parked in an await that is not abort-aware, so recovery meant killing the
process.

Each slot now backs onto a FIFO shared by all three. Requests queue in arrival
order, the head is presented, and answering it resolves that caller and promotes
the next so every caller settles exactly once with its own answer.

The three blocks were near-identical, so they collapse into one useHandlerQueue
helper used three times; the change is a net reduction in the file. The hook's
public shape is unchanged, so App.tsx, chat-input.tsx and interactive-app.tsx
needed no edits.

Scope

Two items from the issue's suggested fix are deliberately not here:

  • A "1 of N" indicator. It changes the hook's public shape and pulls in
    chat-input.tsx and interactive-app.tsx. Worth doing, but it is UX on top of
    the fix rather than part of it.
  • An abort-aware signal(). That changes createGlobalHandlerSlot's
    signature and every call site, including subagent-executor.ts:739 where the
    sub-agent tool bridge work for ACP: thread sub-agent tool results back so every call is announced and settles with its real status #1105 is also editing. Queuing alone removes the
    hang; abort handling is a clean follow-up once that lands.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Changeset

  • Added a changeset (pnpm changeset) describing this change for the changelog

Testing

Automated Tests

  • New features include passing tests in .spec.ts/tsx files
  • All existing tests pass (pnpm test:all completes successfully)
  • Tests cover both success and error scenarios

Manual Testing

  • Tested with Ollama
  • Tested with OpenRouter
  • Tested with OpenAI-compatible API
  • Tested MCP integration (if applicable)

Checklist

  • If this was for an open issue, I was assigned to it
  • Code follows project style guidelines
  • Self-review completed
  • Documentation updated (if needed)
  • No breaking changes (or clearly documented)
  • Appropriate logging added using structured logging (see CONTRIBUTING.md)

The three "ask the user" slots each held a single resolver, so a second
caller arriving before the first was answered overwrote it and that first
promise could never settle. tool-executor starts up to
MAX_CONCURRENT_AGENTS subagents in one turn and awaits them with
Promise.allSettled, so one stranded caller meant the batch never
resolved and the turn never ended. Escape did not help either: the
subagent was parked in an await that is not abort-aware, so recovery
meant killing the process.

Each slot now backs onto a FIFO shared by all three. Requests queue in
arrival order, the head is presented, and answering it resolves that
caller and promotes the next, so every caller settles exactly once with
its own answer. The same single-resolver design backed ask_user and the
main agent's tool confirmation, so both are fixed by the same change.

Closes Nano-Collective#1156
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:tui Terminal UI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Parallel subagents hang the turn, a second approval request overwrites the first

1 participant