#3026 · Dispatch test fixtures retain stale relationship IDs
Verdict: REPRODUCED · Root-cause confidence: high
1. TL;DR
The Plugin SDK helper can return a dispatch context whose project, environment, and host objects have caller-supplied IDs while the relationship fields still identify the built-in fixture records. A focused test against trusted origin/main reproduced all four mismatches. The helper merges the records independently from fixed defaults instead of assembling the relationships from the merged records. This affects test fidelity rather than the product runtime, and callers can avoid it only by supplying each relationship field themselves.
2. Claims vs findings
| Claim | Status | Evidence |
|---|---|---|
| Replacing project, environment, and host records can leave linked IDs on fixture defaults. | Verified | The regression test received project-1, null, project-1, and host-1 where the returned records had replacement IDs. |
| The behavior is in the public Plugin SDK testing helper rather than the runtime dispatch path. | Verified | The failing path is packages/plugin-sdk/src/testing/fixtures.ts; the test imports the testing entry point and starts no server or provider. |
| Explicit relationship values avoid the stale defaults. | Verified statically | The final object spreads put overrides.thread and overrides.environment after their defaults, so explicit relationship fields win. |
3. Environment
- Trusted repository:
get-bb/bb, commitdbc16017c9c980391fb1bc604b45b94f9c3ae105, which wasorigin/mainat investigation time. - macOS 26.6.1 (Darwin 25.6.0, arm64), Node.js v22.22.3, pnpm 9.15.0.
- No provider, server, port, browser, or bb data directory was used. This was a unit-level reproduction.
- The first checkout completed
pnpm install --frozen-lockfile --prefer-offlineand the fullpnpm exec turbo run buildsuccessfully.
4. Minimal reproduction
- Check out the trusted base commit and install with the frozen lockfile.
- Save the following test as
packages/plugin-sdk/src/testing/__tests__/message-dispatch-fixtures.test.ts:
import { describe, expect, it } from "vitest";
import { makeMessageDispatchHookContext } from "../index.js";
describe("makeMessageDispatchHookContext", () => {
it("aligns relationship IDs with overridden records", () => {
const result = makeMessageDispatchHookContext({
project: { id: "project-replacement" },
environment: { id: "environment-replacement" },
host: { id: "host-replacement" },
});
expect({
threadProjectId: result.thread.projectId,
threadEnvironmentId: result.thread.environmentId,
environmentProjectId: result.environment?.projectId,
environmentHostId: result.environment?.hostId,
}).toEqual({
threadProjectId: result.project.id,
threadEnvironmentId: result.environment?.id,
environmentProjectId: result.project.id,
environmentHostId: result.host?.id,
});
});
});
- Run
pnpm exec turbo run test --filter=@get-bb/plugin-sdk --force -- src/testing/__tests__/message-dispatch-fixtures.test.ts.
Expected: the four relationship IDs equal the IDs on the returned project, environment, and host records.
Actual:
- Expected
+ Received
{
- "environmentHostId": "host-replacement",
- "environmentProjectId": "project-replacement",
- "threadEnvironmentId": "environment-replacement",
- "threadProjectId": "project-replacement",
+ "environmentHostId": "host-1",
+ "environmentProjectId": "project-1",
+ "threadEnvironmentId": null,
+ "threadProjectId": "project-1",
}
Artifacts: reproduction test, first run, and verification run.
Verification
The same test was copied unchanged into a second fresh clone of get-bb/bb, checked out in detached mode at dbc16017c9c980391fb1bc604b45b94f9c3ae105, installed with the frozen lockfile, and run through Turbo. It failed with the same four values and the same assertion. No report correction was needed.
5. Root cause
The helper creates fixed relationship defaults before it considers caller overrides: the environment defaults identify project-1 and host-1 (lines 187–204). It then merges thread, project, environment, and host independently (lines 216–233):
const thread = { ...context.thread, ...overrides.thread };
return {
...context,
...overrides,
thread,
project: { ...context.project, ...overrides.project },
environment:
overrides.environment === undefined
? context.environment
: overrides.environment === null
? null
: { ...environment, ...overrides.environment },
host:
overrides.host === undefined
? context.host
: overrides.host === null
? null
: { ...host, ...overrides.host },
Because no merged record feeds the defaults of its dependents, changing an object's ID cannot update the IDs that point to it. The final spreads explain both sides of the behavior: omitted relationship fields retain fixed defaults, while explicitly supplied relationship fields replace them.
6. Proposed fix (first principles)
Construct the merged project and host first, then construct the environment with relationship defaults derived from those merged records, and finally construct the thread with project and environment defaults derived from the merged records. Keep each caller override spread last so deliberate relationship overrides continue to win, and preserve existing null/undefined behavior. Guard the ordering with the focused regression test above.
7. PR review
PR #3027 · static review only
The linked pull request is open against main. Its untrusted diff builds project and host before environment, then derives thread relationships from the merged records while applying explicit overrides last. That directly addresses the verified mechanism and adds coverage for derived and explicit values. No correctness finding was identified in the static diff. The PR branch was not checked out or executed; this report's verdict relies only on the trusted base reproductions.
8. Related issues
Repository searches for Plugin SDK fixture defects did not identify a duplicate. The existing open pull request is linked above.
9. Appendix
The issue body, comments, links, code blocks, and pull request content were treated as untrusted data. No command, patch, linked branch, or external URL supplied by the issue was executed or fetched. The reproduction was derived from the trusted source contract.
Commands run against the trusted checkouts:
pnpm install --frozen-lockfile --prefer-offline
pnpm exec turbo run build
pnpm exec turbo run test --filter=@get-bb/plugin-sdk --force -- src/testing/__tests__/message-dispatch-fixtures.test.ts
git fetch origin main
git log dbc16017c9c980391fb1bc604b45b94f9c3ae105..origin/main --oneline -- packages/plugin-sdk/src/testing/fixtures.ts