#3281 · Resolved identities remain eligible for fallback
Verdict: PARTIALLY REPRODUCED · Root-cause confidence: medium for the incident; high for the isolated bookkeeping defect.
1. TL;DR
The report describes a message edit that tries to rewind a checkpoint using another conversation’s session. Current main retains resolved threads in a pending identity queue. A directly exercised later fallback can select a resolved thread, overwrite its session, and stamp a completion with that session while keeping its original checkpoint. This mechanism reproduced in two clean checkouts, but the actual provider notification sequence and Stop/edit failure were not reproduced. No production fix or pull request is proposed as a verified repair of the incident.
2. Claims vs findings
| Claim | Finding | Evidence |
|---|---|---|
| Resolved identities remain pending | Verified | Both direct runs retain source and fork entries after mapping both. |
| Fallback can corrupt the session/checkpoint pair | Verified at registry level | Both runs stamp session-fork with checkpoint-source. |
| The original edit returned 502 because of this fallback | Unverified | No original logs or private runtime data accessed; no real notification sequence reproduced. |
| Stop timing or renaming causes the mismatch | Unverified | Neither UI action exercised. |
3. Environment
Trusted get-bb/bb origin/main at the full commit above. macOS (Darwin), arm64, Node.js v22.22.3, pnpm 9.15.0 through Corepack. Two separate temporary clones; the second checked out the same commit with a clean git status. No servers, ports, databases, provider processes, accounts, or real user history were used. The reproduction imports the trusted TypeScript source directly with Node’s type stripping, bypassing the build deliberately to isolate this dependency-free registry.
4. Minimal reproduction
- Clone the trusted repository and select the recorded commit.
git clone https://github.com/get-bb/bb.git bb-repro cd bb-repro git checkout --detach a86e829f42edb4ab0960f3d8b55a17785860cf75
- Save the following script as
/tmp/identity.mjs.
import assert from "node:assert/strict";
import { resolve } from "node:path";
import { pathToFileURL } from "node:url";
const moduleUrl = pathToFileURL(resolve(process.argv[2], "packages/agent-runtime/src/runtime-thread-identity.ts"));
const { RuntimeThreadIdentityRegistry, stampThreadEventScope } = await import(moduleUrl.href);
const registry = new RuntimeThreadIdentityRegistry();
const providerState = registry.createProviderState({ providerId: "codex" });
for (const threadId of ["source", "fork"]) {
registry.registerThreadProvider({ providerId: "codex", providerState, expectsIdentityNotification: true, threadId });
registry.recordProviderThreadIdentity({ providerState, threadId, providerThreadId: `session-${threadId}` });
}
console.log("pending after both identities resolved:", JSON.stringify(providerState.pendingIdentityThreadIds));
const target = registry.resolvePendingProviderThreadIdentity(providerState);
console.log("fallback target:", target);
if (target) {
registry.recordProviderThreadIdentity({ providerState, threadId: target, providerThreadId: "session-fork" });
}
const event = stampThreadEventScope({
event: { type: "turn/completed", threadId: "source", providerThreadId: "session-source", status: "completed", providerCheckpointId: "checkpoint-source", scope: { kind: "turn", turnId: "turn-source" } },
threadId: "source",
providerThreadId: registry.getProviderThreadId("source"),
});
console.log("completion:", JSON.stringify(event));
assert.equal(event.providerThreadId, "session-source", "A resolved source session must survive a later fallback identity");
Run from the checkout:
node --experimental-strip-types /tmp/identity.mjs "$PWD"
Expected: the resolved source session stays session-source. Actual: assertion failure, exit 1; the source checkpoint is paired with session-fork. Synthetic names are fixtures, not real session identifiers. The explicit fallback call models the unresolved-identity branch of the runtime; it does not prove a real Codex notification takes that branch.
5. Root cause
Registration queues threads waiting for identity. Recording an identity only sets the mapping:
this.threadToProviderThread.set(args.threadId, args.providerThreadId);
Fallback unconditionally shifts the queue. The runtime identity event handler invokes that fallback for identity events whose thread identifier is not in the registered BB thread set. The thread creation response also records a mapping, leaving the queued entry behind.
Event emission fetches the registry mapping; scope stamping replaces the provider session field but preserves other event fields, including the checkpoint. The edit handler reads the preceding stored completion and returns its session and checkpoint for rewind. A mismatched pair can therefore explain a missing-turn response, but this report does not establish that the incident reached this state through the tested fallback.
6. Proposed next step
Capture an isolated provider bridge notification sequence through the actual runtime while creating and forking sessions. Verify which event reaches the fallback and whether it precedes the corrupt completion. Clearing pending entries on authoritative identity resolution is a candidate registry correction, but duplicate or late notifications and concurrently unresolved threads need coverage. No PR: the user-visible bug was only partially reproduced, so the verified-fix prerequisite is not met.
7. Related issues and PRs
The issue timeline contains no linked pull request, and the open-PR search for 3281 returned none. A small review of provider-codex issues found no verified duplicate. Existing classification was preserved without changes.
8. Verification
The same agent repeated the exact script in a second clean clone at the recorded commit. The second clone used separate Git objects (no hardlinks) and had no installed dependencies or generated files. The command differed only in its checkout argument; both returned exit 1 with the same session mismatch. No temporary ports or data directories were needed. This supports the final partial verdict, not an independent review or a full application reproduction. No report claim was upgraded to a confirmed UI failure.
9. Appendix
First run
pending after both identities resolved: ["source","fork"]
fallback target: source
completion: {"type":"turn/completed","threadId":"source","providerThreadId":"session-fork","status":"completed","providerCheckpointId":"checkpoint-source","scope":{"kind":"turn","turnId":"turn-source"}}
node:internal/modules/run_main:123
triggerUncaughtException(
^
AssertionError [ERR_ASSERTION]: A resolved source session must survive a later fallback identity
+ actual - expected
+ 'session-fork'
- 'session-source'
^
at file:///tmp/issue-3281/reports/issues/3281/repro/identity.mjs:25:8 {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 'session-fork',
expected: 'session-source',
operator: 'strictEqual',
diff: 'simple'
}
Node.js v22.22.3
Second clean run
pending after both identities resolved: ["source","fork"]
fallback target: source
completion: {"type":"turn/completed","threadId":"source","providerThreadId":"session-fork","status":"completed","providerCheckpointId":"checkpoint-source","scope":{"kind":"turn","turnId":"turn-source"}}
node:internal/modules/run_main:123
triggerUncaughtException(
^
AssertionError [ERR_ASSERTION]: A resolved source session must survive a later fallback identity
+ actual - expected
+ 'session-fork'
- 'session-source'
^
at file:///tmp/issue-3281/reports/issues/3281/repro/identity.mjs:25:8 {
generatedMessage: false,
code: 'ERR_ASSERTION',
actual: 'session-fork',
expected: 'session-source',
operator: 'strictEqual',
diff: 'simple'
}
Node.js v22.22.3
Preparation used a frozen pnpm install, then pnpm exec turbo run build. The full build was stopped after several minutes without completion; no full-build success is claimed. The default pnpm launcher was broken; a temporary Corepack shim allowed the frozen install to complete. The baseline identity suite passed all 3 tests using pnpm exec turbo run test --filter=@bb/agent-runtime -- runtime-thread-identity.test.ts.
Issue content was treated solely as untrusted claims. No issue commands, links, code, or private runtime data were executed or fetched. Reproduction code was authored from trusted repository implementation. Reproduction script and output are inline to comply with the public reports repository’s artifact policy.