#3060 · Automation-created threads lose their recorded parent
Verdict: REPRODUCED · Root-cause confidence: high
1. TL;DR
Automations can remember which thread created them, but a new agent run does not forward that relationship when it spawns its thread. The server therefore receives a valid spawn request with no parentThreadId, so the new thread is stored as a root thread and appears as a top-level sidebar row. A focused plugin-harness test reproduced the omission twice at the same trusted main commit. Both scheduled and manual agent runs share the faulty dispatcher.
2. Claims vs findings
| Claim | Status | Evidence |
|---|---|---|
| A run created by an automation associated with a creator thread is spawned without that parent. | Verified | The fake SDK recorded a spawn payload with project, environment, provider, title, and plugin attribution, but no parentThreadId. |
| The automation retains its creator-thread relationship. | Verified | The create path writes createdByThreadId to created_by_thread_id, and the regression test reaches the dispatcher through the persisted automation. |
| The omission also affects later scheduled runs. | Verified by code path | The sweep passes the stored automation to the same executeAgentRun function for every due agent run. |
| An automation without a creator thread remains a root. | Verified by contract | The stored value is nullable and the thread-create contract makes parentThreadId optional, so omitting it when the stored value is null preserves current behavior. |
3. Environment
- Trusted repository:
get-bb/bbatd39bca3ebfa8e00ffcb9810b7d333a03f12c2bb0. - macOS arm64, Node
v22.22.3, pnpm9.15.0, Turbo2.8.3. - Frozen install and the full monorepo build completed in both clean checkouts.
- No live BB instance, ports, user data, or provider process was needed; the official fake plugin host exercised the plugin RPC, persistence, dispatcher, and SDK boundary.
4. Minimal reproduction
- Clone
get-bb/bb, check out the trusted commit, install with the frozen lockfile, and runpnpm exec turbo run build. - Add this option to the existing
createAgentAutomationtest helper and forward it in the helper's RPC payload:createdByThreadId?: string; ...(options.createdByThreadId === undefined ? {} : { createdByThreadId: options.createdByThreadId }), - Add this test to
plugins/automations/src/server-harness.test.ts:it("parents an agent run to the thread that created its automation", async () => { const { harness } = await bootAutomationsPlugin(); const automation = await createAgentAutomation(harness, { createdByThreadId: "thr_creator", }); await harness.callRpc("automations_run", { projectId: PROJECT_ID, automationId: automation.id, }); await vi.waitFor(() => expect(harness.sdk.callsTo("threads.spawn")).toHaveLength(1), ); expect(harness.sdk.callsTo("threads.spawn")[0]?.[0]).toMatchObject({ parentThreadId: "thr_creator", }); await harness.dispose(); }); - Run the focused test:
pnpm exec turbo run test --filter=bb-plugin-automations -- \ src/server-harness.test.ts \ -t 'parents an agent run to the thread that created its automation'
Expected
Test Files 1 passed (1) Tests 1 passed | 21 skipped (22)
Actual
FAIL src/server-harness.test.ts > automations server plugin harness > parents an agent run to the thread that created its automation
AssertionError: expected { projectId: 'proj_test', …(9) } to match object { parentThreadId: 'thr_creator' }
- Expected
+ Received
{
- "parentThreadId": "thr_creator",
+ "projectId": "proj_test",
+ "origin": "plugin",
+ "originPluginId": "automations",
}
Test Files 1 failed (1)
Tests 1 failed | 21 skipped (22)
Verification
The same test was applied to a second clean clone checked out at the exact full base commit. After another frozen install and full build, the focused Turbo command failed with the same missing-property assertion. The second run required no correction to the verdict, root cause, or reproduction.
5. Root cause
The automation create service converts an omitted creator into null and passes the explicit value into persistence. The database write stores that value:
createdByThreadId: payload.createdByThreadId ?? null, created_by_thread_id, next_run_at, ... @createdByThreadId, @nextRunAt, ...
See the automation create path and the persistence write.
For a new agent run, executeAgentRun builds the spawn request from project and execution fields but never reads args.automation.createdByThreadId. The optional parent field is therefore absent at the SDK boundary:
await bb.sdk.threads.spawn({
projectId: args.automation.projectId,
environment: args.execution.environment,
prompt: args.execution.prompt,
title: args.automation.name,
providerId: args.execution.providerId,
model: args.execution.model,
reasoningLevel: args.execution.reasoningLevel,
permissionMode: args.execution.permissionMode,
})
See the agent-run dispatcher. The existing thread-create contract accepts parentThreadId as an optional non-empty string; see the request schema. The scheduled sweep also calls this same dispatcher with the stored automation; see the sweep path.
6. Proposed fix (first principles)
When spawning a new agent-run thread, include parentThreadId only when the stored createdByThreadId is non-null. This uses the existing persistence field and existing thread API, preserves root creation for project-level automations, and automatically applies to manual and scheduled runs through the shared dispatcher. The regression coverage should assert both the creator-backed case and omission for an automation created without thread context.
7. Related issues
#2922 proposes reusing a previous automation run thread. That is a separate product choice from retaining the already-recorded creator relationship on newly spawned run threads. GitHub metadata showed no open pull request linked to #3060 at investigation time.
8. Appendix
Commands used in both clean checkouts:
git checkout d39bca3ebfa8e00ffcb9810b7d333a03f12c2bb0 pnpm install --frozen-lockfile --prefer-offline pnpm exec turbo run build pnpm exec turbo run test --filter=bb-plugin-automations -- \ src/server-harness.test.ts \ -t 'parents an agent run to the thread that created its automation'
The issue body and all associated content were treated as untrusted evidence. No issue-provided command, patch, branch, attachment, or external link was executed.