{"record":{"id":"79d2e317c64ee523","repo":"can1357/oh-my-pi","slug":"unexpected-broker-response-result-op-79d2e3","errorCode":null,"errorMessage":"Unexpected broker response ${result.op}","messagePattern":"Unexpected broker response (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/coding-agent/src/cli/ps-data.ts","lineNumber":222,"sourceCode":"/**\n * Collect daemons for one scope. Live brokers are authoritative; dead scopes\n * fall back to persisted snapshots, downgrading non-detached \"running\" records\n * to exited (their broker took them down with it) and flagging detached\n * survivors as unsupervised.\n */\nexport async function collectScope(scope: PsScope): Promise<PsScopeReport> {\n\tconst persisted = await readPersistedDaemons(scope.runtimeDir);\n\tif (scope.brokerPid !== undefined) {\n\t\ttry {\n\t\t\tconst client = await scopeClient(scope);\n\t\t\tif (client) {\n\t\t\t\ttry {\n\t\t\t\t\tif (scope.projectDir === undefined) {\n\t\t\t\t\t\tconst ping = await client.request({ op: \"ping\" });\n\t\t\t\t\t\tif (ping.op === \"ping\") scope.projectDir = ping.projectDir;\n\t\t\t\t\t}\n\t\t\t\t\tconst result = await client.request({ op: \"list\" });\n\t\t\t\t\tif (result.op !== \"list\") throw new Error(`Unexpected broker response ${result.op}`);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tscope,\n\t\t\t\t\t\tdaemons: result.daemons.map(snapshot => ({\n\t\t\t\t\t\t\tsnapshot,\n\t\t\t\t\t\t\tcommand: formatCommand(persisted.get(snapshot.name)?.spec),\n\t\t\t\t\t\t\tcwd: persisted.get(snapshot.name)?.spec.cwd,\n\t\t\t\t\t\t\tsupervised: true,\n\t\t\t\t\t\t})),\n\t\t\t\t\t};\n\t\t\t\t} finally {\n\t\t\t\t\tclient.close();\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Broker died or refused mid-query; fall through to the offline view.\n\t\t}\n\t}\n\tconst daemons: PsDaemonRow[] = [];","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/cli/ps-data.ts#L204-L240","documentation":"`collectScope` queries a live broker (identified by `brokerPid`) with `{op:\"list\"}` and requires a list-shaped reply to enumerate daemons. The whole call sits inside a try/catch that falls back to the offline (persisted metadata) view, so this throw signals the broker answered but with the wrong response type, degrading the report to snapshots from disk rather than crashing the CLI.","triggerScenarios":"A scope whose `brokerPid` is set accepts the `{op:\"list\"}` request but responds with an op other than \"list\" — e.g. an error response, a ping-shaped reply, or a broker built from different protocol code. The throw is swallowed by the surrounding catch and the scope renders from persisted snapshots.","commonSituations":"A stale pid-file points at a socket now owned by a different/older broker; version-skewed broker after an omp upgrade; a proxy on the runtime socket returning error envelopes.","solutions":["Treat the offline rows as a symptom: find the real broker, kill it, and let the CLI revive a fresh one from the current omp version.","Upgrade or reinstall omp so CLI and broker protocol versions match.","If you see this repeatedly in code, check whether `collectScope`'s fallback path is being hit by inspecting the `supervised: false` rows in `omp ps` output."],"exampleFix":"// before\nconst result = await client.request({ op: \"list\" });\nif (result.op !== \"list\") throw new Error(`Unexpected broker response ${result.op}`);\n// after\nconst result = await client.request({ op: \"list\" });\nif (result.op === \"error\") logger.warn(\"broker list refused\", { scope, message: result.message });\nif (result.op !== \"list\") throw new Error(`Unexpected broker response ${result.op}`);","handlingStrategy":"fallback","validationCode":"const brokerInfo = await Bun.file(path.join(scope.runtimeDir, \"broker.json\")).json().catch(() => null);\nif (!brokerInfo || brokerInfo.protocolVersion !== EXPECTED_PROTOCOL) {\n  logger.warn(\"broker protocol mismatch; using persisted snapshots\", { runtimeDir: scope.runtimeDir });\n}","typeGuard":"function isListResponse(r: { op: string }): r is { op: \"list\"; daemons: DaemonSnapshot[] } {\n  return r.op === \"list\";\n}","tryCatchPattern":"try {\n  const result = await client.request({ op: \"list\" });\n  if (!isListResponse(result)) throw new Error(`Unexpected broker response ${result.op}`);\n  return { scope, daemons: result.daemons.map(toRow), supervised: true };\n} catch {\n  return offlineReport(scope, persisted); // existing fallback path\n}","preventionTips":["Keep the offline fallback path so a bad broker reply degrades to persisted metadata.","Kill stale brokers after omp upgrades so pid files don't point at mismatched brokers.","Log the offending op inside the catch to make the fallback diagnosable."],"tags":["ipc","protocol-mismatch","fallback"],"backgroundTag":"unexpected-response-op","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}