#3151 · ACP steering cancels the active prompt
Verdict: REPRODUCED · Root-cause confidence: high
1. TL;DR
Sending a steer to an active ACP-backed thread cancels the provider’s current session/prompt request. BB then sends only the steer text in a second session/prompt, so an ACP agent can abandon unfinished work from the original request and respond only to the new message. A focused fake-agent test records both prompt requests and proves that session/cancel appears between them on trusted main. The same test failed identically in a second clean checkout.
2. Claims vs findings
| Paraphrased claim | Status | Evidence |
|---|---|---|
| An active steer interrupts the previous ACP request. | Verified | The ACP request log contains session/cancel after the first prompt and before the steered prompt. |
| The agent may handle only the steered message. | Verified | The existing fake-agent path leaves the first prompt incomplete when cancelled, while the second prompt receives only the steer input. Existing bridge coverage explicitly expects no completed response for a cancelled hanging prompt. |
| The behavior occurs on every attempt. | Unverified frequency | Both controlled runs reproduced deterministically, but no statistical or real-account frequency study was performed. |
| Desktop and mobile exhibit the same behavior. | Verified by shared path | Both clients submit through the same server/runtime ACP bridge; cancellation is performed inside the provider bridge, below either UI. |
3. Environment
- Trusted repository:
get-bb/bbat39636049b3d6eefb3dd1601035675afa52f22b72 - macOS 26.6.1, arm64; Node.js 22.22.3; pnpm workspace install with frozen lockfile
- Provider: repository fake ACP agent driven through the real ACP bridge; no provider account or network model call
- No app server was started, so no ports or persistent data directory were used
- Initial repository build: 20 tasks passed
4. Minimal reproduction
- Check out the trusted base commit and prepare it:
git checkout 39636049b3d6eefb3dd1601035675afa52f22b72 pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build
- Apply the test block below to
packages/provider-bridge-acp/src/bridge/bridge.test.tsimmediately before the stacked-steers test, or use the saved complete test file. - Run only the regression test:
pnpm exec turbo run test --filter=@bb/provider-bridge-acp -- src/bridge/bridge.test.ts -t 'preserves the active ACP prompt when steering'
Regression test
it("preserves the active ACP prompt when steering", async () => {
const requestLog = join(workspaceDir, "steer-request-log.jsonl");
const promptLog = join(workspaceDir, "steer-prompt-log.jsonl");
const { providerThreadId } = await startThread({
envVars: {
FAKE_ACP_REQUEST_LOG: requestLog,
FAKE_ACP_PROMPT_LOG: promptLog,
},
});
const turnId = sendTurnRequest("turn/start", providerThreadId, {
input: [{ type: "text", text: "slow first", mentions: [] }],
});
await waitForResponse(turnId);
const steerId = sendTurnRequest("turn/steer", providerThreadId, {
expectedTurnId: "turn-1",
input: [{ type: "text", text: "steered", mentions: [] }],
});
await waitForResponse(steerId);
await waitForTurnCompleted();
expect(loggedPrompts(promptLog)).toEqual(["slow first", "steered"]);
expect(
loggedAcpRequests(requestLog).map((request) => request.method),
).not.toContain("session/cancel");
});
Expected vs actual
Expected: the ACP request sequence does not contain session/cancel.
Actual: AssertionError: expected [ 'initialize', 'session/new', …(3) ]
to not include 'session/cancel'
Result: 1 failed | 95 skipped
This is a behavioral wire test rather than a visual test, so no screenshot is required.
5. Verification
The reproduction was repeated by the same investigator in a fresh clone checked out detached at the same full SHA. After a separate frozen install, the same focused Turbo/Vitest command failed on the same session/cancel assertion with 1 failed | 95 skipped. No report claim was weakened or corrected after the second run.
6. Root cause
The turn/steer handler first appends the new input to queuedInputs, then immediately calls requestSteerCancel. That helper marks the live prompt cancelled and sends an ACP session/cancel notification. See the steer handler and the cancellation helper.
session.queuedInputs.push({ input: params.input, ... });
requestSteerCancel(session);
session.connection.notify("session/cancel", {
sessionId: session.providerThreadId,
});
After the cancelled request resolves, the turn loop shifts the queued input and starts a new session/prompt carrying only that input. It does not combine the unfinished original input with the steer. See the prompt loop. The bridge also advertises steerMode: "queue" in its capabilities, but implements an interrupting cancel-then-prompt sequence. This behavior was deliberately introduced by merged PR #1477 because ACP v1 has no native mid-prompt injection.
7. Proposed fix (first principles)
Choose and document one ACP-specific contract. To preserve the active request, leave the steer in queuedInputs, wait for the current session/prompt to finish, and then submit the queued input as the next prompt without calling session/cancel. That matches the bridge’s advertised queue mode, but it means a hung ACP prompt cannot be interrupted by steering. Alternatively, retain hard-steer cancellation and expose that limitation clearly in the product. Selecting between responsiveness and preservation is a product/architecture decision, so no production patch should be made automatically.
8. Related issues and changes
- PR #1477 introduced the cancel-then-prompt emulation and documents the ACP protocol limitation.
- No open pull request links to issue #3151, and no later trusted-main commit changes the relevant bridge or regression-test paths.
9. Appendix
Commands run:
git fetch https://github.com/get-bb/bb.git refs/heads/main:refs/remotes/origin/main pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build pnpm exec turbo run test --filter=@bb/provider-bridge-acp -- src/bridge/bridge.test.ts -t 'preserves the active ACP prompt when steering' git clone https://github.com/get-bb/bb.git <fresh-temporary-checkout> git checkout --detach 39636049b3d6eefb3dd1601035675afa52f22b72 pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run test --filter=@bb/provider-bridge-acp -- src/bridge/bridge.test.ts -t 'preserves the active ACP prompt when steering'
Untrusted-data note: issue text was used only as a claim to test. The external attachment was not opened, no issue-provided command or code was run, and no linked pull request branch was checked out.