#2460 · thread.start fails with “Internal error” when omp uses Ox Alpha
Verdict: ALREADY FIXED · Root-cause confidence: high
1. TL;DR
bb 0.39.0 gave omp the provider-bridge bootstrap as a dynamic-tool MCP server.
The bootstrap did not accept the MCP flag, so it stopped before the ACP session opened.
OMP returned bb-bridge: Transport closed, but bb 0.39.0 removed that useful text.
The selected Ox Alpha model did not cause this failure.
Commit aab0cd710d fixed both faults before this report base.
2. Claims vs findings
| Claim from the issue | Status | Evidence |
|---|---|---|
bb 0.39.0 returns HTTP 502 and -32000 Internal error at thread.start. | Verified | The exact 0.39.0 bridge and OMP 18.0.5 returned the same JSON-RPC error. See the live output. |
| The failure occurs before a model response. | Verified | The raw ACP response contains error.data.details: "bb-bridge: Transport closed" during session/new. See the raw ACP and bootstrap output. |
| The failure is not transient. | Verified | The faulty MCP command always names the wrong program in the 0.39.0 process layout. |
| Ox Alpha or OpenRouter causes the failure. | Refuted | The minimal run used no OpenRouter account and no selected model. It failed before model work. |
| The provider request never starts. | Verified | The bootstrap exited with status 1 and printed its usage error. OMP then rejected the ACP session. |
3. Environment
- Fault run: bb
desktop-v0.39.0, commitb33abbff098a. - Fix check: bb commit
ad79bbb5ec909524f8f281e62d860c588a86f332. - Agent: official OMP Linux x64 release
omp/18.0.5. - Host: Linux x86_64, kernel
7.0.0-29-generic. - Node:
v24.18.0. - No bb dev server ran. The test used no bb dev ports.
- The run used separate temporary OMP profile, workspace, and bridge data directories.
4. Minimal reproduction
The script starts the 0.39.0 bridge through its real worker entry. It starts OMP 18.0.5 with one dynamic tool.
- Prepare the old source and exact OMP release.
git clone https://github.com/get-bb/bb.git bb-2460 cd bb-2460 git checkout desktop-v0.39.0 pnpm install --frozen-lockfile --prefer-offline gh release download v18.0.5 -R can1357/oh-my-pi -p omp-linux-x64 -D .repro-bin chmod u+x .repro-bin/omp-linux-x64
- Download the repro script into the checkout.
curl --fail --location \ https://get-bb.github.io/reports/issues/2460/repro/repro-v039-omp.mjs \ --output ./repro-v039-omp.mjs
- Run the script with separate temporary directories.
mkdir -p .repro-profile .repro-workspace .repro-data node repro-v039-omp.mjs "$PWD" \ "$PWD/.repro-bin/omp-linux-x64" \ "$PWD/.repro-profile" \ "$PWD/.repro-workspace" \ "$PWD/.repro-data"
Expected: thread.start returns a provider thread ID.
Actual:
bridge stdout: {"jsonrpc":"2.0","id":2,"error":{"code":-32000,"message":"Internal error"}}
thread.start result: {"jsonrpc":"2.0","id":2,"error":{"code":-32000,"message":"Internal error"}}
raw ACP stdout:
{"jsonrpc":"2.0","id":2,"error":{"code":-32603,"message":"Internal error","data":{"details":"bb-bridge: Transport closed"}}}
bootstrap command exit: 1
bootstrap stderr: provider bridge bootstrap usage: <bridgeModulePath> <pluginId> <pluginDataDir> (absolute paths)
Base fix check
git checkout ad79bbb5ec909524f8f281e62d860c588a86f332 pnpm install --frozen-lockfile --prefer-offline cd packages/provider-bridge-acp pnpm exec vitest run --config vitest.config.ts \ src/bridge/mcp-server-entry.test.ts \ src/bridge/agent-connection.test.ts
Test Files 2 passed (2) Tests 7 passed (7)
See the base test output.
Repro script
The saved artifact contains the full script. The script traces OMP output and runs the faulty MCP child command.
import { spawn, spawnSync } from "node:child_process";
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { resolve } from "node:path";
import { createInterface } from "node:readline";
import { pathToFileURL } from "node:url";
const [repoRoot, ompBinary, profileDir, workspaceDir, dataDir] =
process.argv.slice(2);
if (!repoRoot || !ompBinary || !profileDir || !workspaceDir || !dataDir) {
process.stderr.write(
"usage: node repro-v039-omp.mjs <repo> <omp> <profile> <workspace> <data-dir>\n",
);
process.exit(2);
}
const requireFromRepo = createRequire(
pathToFileURL(resolve(repoRoot, "package.json")),
);
const tsxLoader = requireFromRepo.resolve("tsx");
const workerEntry = resolve(
repoRoot,
"packages/provider-bridge-protocol/src/bridge-worker-entry.ts",
);
const bridgeModule = resolve(
repoRoot,
"plugins/provider-acp/src/bridge/bridge.ts",
);
const proxyPath = resolve(dataDir, "omp-stdio-proxy.mjs");
const tracePath = resolve(dataDir, "omp-stdout.jsonl");
writeFileSync(
proxyPath,
`import { spawn } from "node:child_process";
import { createWriteStream } from "node:fs";
const [command, ...args] = process.argv.slice(2);
const trace = createWriteStream(process.env.BB_REPRO_OMP_STDOUT_TRACE);
const child = spawn(command, args, {
env: process.env,
stdio: ["pipe", "pipe", "pipe"],
});
process.stdin.pipe(child.stdin);
child.stdout.on("data", (chunk) => {
trace.write(chunk);
process.stdout.write(chunk);
});
child.stderr.pipe(process.stderr);
child.on("exit", (code) => {
trace.end(() => process.exit(code ?? 1));
});
`,
);
const bridge = spawn(
process.execPath,
[
"--conditions=source",
"--import",
tsxLoader,
workerEntry,
bridgeModule,
"provider-acp",
dataDir,
],
{ stdio: ["pipe", "pipe", "pipe"] },
);
let bridgeStderr = "";
bridge.stderr.on("data", (chunk) => {
bridgeStderr += chunk.toString();
});
const responses = new Map();
createInterface({ input: bridge.stdout }).on("line", (line) => {
process.stdout.write(`bridge stdout: ${line}\n`);
try {
const message = JSON.parse(line);
if (message.id !== undefined && message.method === undefined) {
responses.set(message.id, message);
}
} catch {
// Keep non-JSON output in the transcript.
}
});
function send(id, method, params) {
bridge.stdin.write(
`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}\n`,
);
}
async function waitForResponse(id, timeoutMs = 30000) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const response = responses.get(id);
if (response) return response;
await new Promise((resolveDelay) => setTimeout(resolveDelay, 20));
}
throw new Error(
`timed out waiting for response ${id}; bridge stderr: ${bridgeStderr}`,
);
}
try {
send(1, "initialize", {
protocolVersion: 1,
client: { name: "bb-repro", version: "0" },
});
await waitForResponse(1);
send(2, "thread/start", {
threadId: "thr_repro_2460",
cwd: workspaceDir,
instructionMode: "append",
options: {
permissionMode: "full",
permissionScope: "full",
approvalReviewer: null,
permissionEscalation: null,
providerOptions: {
acpLaunchSpec: {
displayName: "omp",
command: process.execPath,
args: [proxyPath, ompBinary, "acp"],
env: {
BB_REPRO_OMP_STDOUT_TRACE: tracePath,
PI_CODING_AGENT_DIR: profileDir,
},
},
},
},
dynamicTools: [
{
name: "repro_tool",
description: "A minimal dynamic tool for the MCP bridge.",
inputSchema: { type: "object", properties: {} },
},
],
});
const start = await waitForResponse(2);
process.stdout.write(`thread.start result: ${JSON.stringify(start)}\n`);
await new Promise((resolveDelay) => setTimeout(resolveDelay, 100));
const rawAcpOutput = existsSync(tracePath)
? readFileSync(tracePath, "utf8").trim()
: "trace file was not created";
process.stdout.write(`raw ACP stdout:\n${rawAcpOutput}\n`);
const bootstrap = spawnSync(
process.execPath,
[
"--conditions=source",
"--import",
tsxLoader,
workerEntry,
"--mcp-stdio",
],
{ cwd: repoRoot, encoding: "utf8" },
);
process.stdout.write(`bootstrap command exit: ${bootstrap.status}\n`);
process.stdout.write(`bootstrap stdout: ${bootstrap.stdout.trim()}\n`);
process.stdout.write(`bootstrap stderr: ${bootstrap.stderr.trim()}\n`);
process.stdout.write(`bridge stderr: ${bridgeStderr.trim()}\n`);
} finally {
bridge.kill("SIGKILL");
}
5. Root cause
bb 0.39.0 ran each provider bridge through a general worker program.
The ACP bridge used process.argv[1] as the dynamic-tool MCP program.
That value named the worker, not the ACP bridge module.
The worker received --mcp-stdio, printed its usage text, and stopped.
OMP then rejected ACP session/new because its required bb-bridge transport closed.
The direct run confirms both events. The raw ACP response contains bb-bridge: Transport closed.
The same MCP child command exits with status 1 and prints the bootstrap usage error.
See the raw ACP and bootstrap output.
The faulty code appears in the 0.39.0 bridge.
const entryPoint = process.argv[1] ? resolve(process.argv[1]) : fileURLToPath(import.meta.url); return [...process.execArgv, entryPoint, "--mcp-stdio"];
The base code uses its module URL. Its comment records the old failure.
// process.argv[1] is the provider-bridge bootstrap, not this module. return [...process.execArgv, fileURLToPath(import.meta.url), "--mcp-stdio"];
A second fault hid the cause.
The 0.39.0 parser kept only error.message and removed error.data.details.
See the old parser.
The base parser now adds the details. See agent-connection.ts lines 109–120.
const details = formatAgentErrorData(error.data);
return details === undefined ? message : `${message}: ${details}`;
This shared path affected all ACP agents that required the session MCP server.
Ox Alpha only identified the model that the reporter selected.
6. Proposed fix (first principles)
No new code change is necessary.
Commit aab0cd710d implemented the correct fix.
It uses import.meta.url for the MCP child and keeps ACP error details.
Users must update from bb 0.39.0 to bb 0.40.0 or a later release.
Close #2460 as already fixed and as a duplicate of #1918.
7. Related issues
- #1918 · internal error when use omp Agent has the same fault and exact wire error.
- #1932 · Run the ACP bb-bridge MCP server from the bridge module merged the fix.
- OMP v18.0.5 supplied the exact agent binary.
8. Appendix
Artifacts
- repro-v039-omp.mjs
- v039-live-output.txt
- v039-raw-acp-bootstrap-output.txt
- base-targeted-test-output.txt
Important commands
gh issue view 2460 --comments pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build git checkout desktop-v0.39.0 node repro-v039-omp.mjs <repo> <omp> <profile> <workspace> <data-dir> git checkout ad79bbb5ec909524f8f281e62d860c588a86f332 git fetch origin main pnpm exec vitest run --config vitest.config.ts src/bridge/mcp-server-entry.test.ts src/bridge/agent-connection.test.ts
Caveat: The run did not use the reporter’s OpenRouter account.
This limit does not affect the conclusion because the fault occurs before model work.
Verification
The independent verifier used an external package store because the shared /tmp file system had no free file records.
The verifier ran the script and received -32000 Internal error. The verifier also ran all seven base tests.
This revision added a copy command. It also added direct raw ACP and bootstrap evidence from a new clean run.