#2292 · prompt history: empty-array rows persisted then skipped at read (zod too_small; 11 rows observed)
Verdict: REPRODUCED (live on a dev instance and with a unit test at the exact write path) · Root-cause confidence: high
1. TL;DR
bb remembers every prompt a user sends so the composer can recall it later ("prompt history", table prompt_history_entries). A thread can legitimately be started without a prompt: bb thread fork <id> with no --prompt, the SDK's threads.fork without input, or the side-chat panel's launcher fork, all clone the source provider session and leave the new thread idle. For those starts the server still writes a prompt-history row whose stored input is the JSON string []. When history is read back, the row is parsed with a schema that requires at least one item, so it fails validation, is dropped, and a WARN Skipping malformed prompt history row is logged on every read for as long as the row exists. Nothing crashes and the user sees nothing wrong (the app also drops empty drafts client-side); the defect is a wasted row plus perpetual log noise. The cause is that the single write funnel recordAcceptedPromptHistoryEntry persists args.input unconditionally, and a no-prompt fork is user-initiated and root-level (forks are not hierarchy children), so it resolves to the project scope and is written. PR #2295 adds an input.length === 0 guard at that funnel; I verified it fixes the write path in unit tests and live.
2. Claims vs findings
| Claim from the issue | Status | Evidence |
|---|---|---|
Threads started without a first prompt get a prompt-history row with an empty input array | Verified | Live: after bb thread fork thr_kqdsa8qm7t --workspace reuse (no --prompt) the table holds phist_deg4hty4uq | project | thr_hz6xuhkvky | 6 | [] (section 4, step 5). |
Read path validates with z.array(promptInputSchema).min(1), fails with "Too small", skips the row and logs a warn | Verified | storedPromptHistoryInputSchema at prompt-history.ts#L24; server log line in section 4 step 6 shows exactly "code": "too_small", "minimum": 1. |
Write funnel is recordAcceptedPromptHistoryEntry and all four call sites route through it | Verified | grep -rn recordAcceptedPromptHistoryEntry apps/server/src: thread-provisioning.ts:254 and :313, thread-send.ts:366, queued-messages.ts:355. Nothing else inserts into the table in production code. |
createThreadRequestSchema deliberately allows input: [] when originKind !== null | Verified | threads.ts#L113-L117 (no .min(1)) and the refinement at #L146-L152. Also forkThreadRequestSchema.input is optional (#L177) and thread-fork.ts defaults it to []. |
| Runtime has a no-input-no-turn guard (thread-lifecycle.ts L733-L744), so the turn never runs but the history row was already written | Verified | thread-lifecycle.ts#L731-L744: command.fork && command.input.length === 0 → run.succeeded. Live: the fork thread landed idle with no provider turn; the history row had been written at provision time (request sequence 6). |
| "Source-derived side-chat preloads" are an affected path | Partially verified | Only side chats created without a reply seed hit the user-initiated write. plugins/side-chat/server.ts sends agentContextSeed when an anchor exists; thread-fork.ts#L112-L115 then sets startedOnBehalfOf: {initiator: "agent"} and the scope resolver returns null (no row). The launcher tip fork ("no anchor, no seed", plugins/side-chat/app.tsx#L345) and anchors equal to the last message (resolveReplySeedText → null) do reach the write with initiator: "user", so the claim holds for those. Not driven through the UI here; the CLI idle fork is the same server path. |
11 malformed rows observed in production data on 2026-08-22, provider acp-omp | Unverified | Private database; not available to me. Mechanism confirmed independently, provider-agnostic. |
| Not provider-specific | Verified | Reproduced with codex. The write happens in requestThreadProvision before any provider command is issued. |
Still present on main | Verified | Present at 494f66526. git log 494f66526..origin/main -- apps/server/src/services/prompt-history.ts apps/server/src/services/threads packages/db/src packages/domain/src is empty as of 2026-08-24 (origin/main = 21cb6b68b), so not fixed upstream. |
| Not a duplicate of #2190 / #1321 | Verified | #2190 is a SQLite database-reversion incident; #1321 is a merged host-daemon event-delivery fix. Neither concerns prompt-history writes. |
| Low user impact, no workaround needed | Verified | The app's promptHistoryEntriesToDrafts (apps/app/src/lib/prompt-history.ts) skips empty drafts anyway, so even without the read-side guard nothing empty would be shown. The DB fetch window is limit + 50 rows (rawPromptHistoryRowLimit), so a handful of bad rows cannot crowd out visible entries. Impact is one wasted row per idle fork and one warn line per history read. |
3. Environment
- bb source at
494f66526913557ab076e048218236f0a6610927(main, 2026-08-24), own worktree, detached HEAD. - macOS 26.5.2 (Darwin 25.5.0), Apple Silicon; Node v22.23.1; pnpm 9.15.0; codex-cli 0.149.1 (provider
codex, model gpt-5.6-sol, permission mode accept-edits). - Dev instance from
scripts/bb-dev-app current: Apphttp://localhost:15422, Serverhttp://localhost:23422, Host daemonhttp://127.0.0.1:31422, data dir~/.bb-dev/bb-machines-HOST.getbb.app-checkouts-bb-.claude-worktrees-wf_846839f8-f8a-63-d4073e0642a3(deleted after the investigation). Host idhost_6ntxdnapd7, projectproj_ba66tywb3eon scratch repo/tmp/bb-2292-qa.
4. Minimal reproduction
4a. Live (CLI + sqlite3), ~1 minute, one tiny codex turn
- Start an isolated dev instance from a checkout of
494f66526and create a project on it.pnpm install --frozen-lockfile --prefer-offline && pnpm exec turbo run build scripts/bb-dev-app current # prints App/Server/Host daemon URLs and the data dir mkdir -p /tmp/bb-2292-qa && cd /tmp/bb-2292-qa && git init -q -b main . && echo "# qa" > README.md && git add . && git commit -qm init curl -s http://localhost:23422/api/v1/hosts # copy the host id curl -s -X POST http://localhost:23422/api/v1/projects -H 'content-type: application/json' \ -d '{"name":"qa","source":{"type":"local_path","path":"/tmp/bb-2292-qa","hostId":"host_6ntxdnapd7"}}' # → {"id":"proj_ba66tywb3e", ...} - Spawn a source thread with a provider that supports fork (codex, claude-code, pi) and wait until it is idle.
BB_SERVER_URL=http://localhost:23422 BB_HOST_DAEMON_PORT=31422 BB_PROJECT_ID=proj_ba66tywb3e \ pnpm bb:dev thread spawn --project proj_ba66tywb3e --provider codex --permission-mode accept-edits \ --title "2292 source" --prompt "Reply only with ok." --json # → "id": "thr_kqdsa8qm7t" curl -s http://localhost:23422/api/v1/threads/thr_kqdsa8qm7t | python3 -c 'import sys,json; print(json.load(sys.stdin)["status"])' # → idle
- Look at the table before forking: one valid row for the source prompt.
DB=<data dir>/bb.db sqlite3 -header "$DB" "select id, scope, thread_id, request_sequence, input from prompt_history_entries order by created_at" id|scope|thread_id|request_sequence|input phist_jxd9x97ahk|project|thr_kqdsa8qm7t|1|[{"type":"text","text":"Reply only with ok.","mentions":[]}] - Fork the thread without a prompt (an "idle fork", exactly what the CLI help offers:
--prompt"Optional first prompt; omit for an idle fork").BB_SERVER_URL=http://localhost:23422 BB_HOST_DAEMON_PORT=31422 BB_PROJECT_ID=proj_ba66tywb3e \ node packages/scripts/dist/commands/run-cli.js thread fork thr_kqdsa8qm7t --workspace reuse --title "2292 idle fork" --json # → "id": "thr_hz6xuhkvky", "parentThreadId": null, "sourceThreadId": "thr_kqdsa8qm7t", "originKind": "fork"
- Look at the table again.
sqlite3 -header "$DB" "select id, scope, thread_id, request_sequence, input from prompt_history_entries order by created_at" expected: only the one valid row from step 3 (there was no prompt to remember) actual: id|scope|thread_id|request_sequence|input phist_jxd9x97ahk|project|thr_kqdsa8qm7t|1|[{"type":"text","text":"Reply only with ok.","mentions":[]}] phist_deg4hty4uq|project|thr_hz6xuhkvky|6|[] - Read project prompt history through the API, then look at the server log.
curl -s http://localhost:23422/api/v1/projects/proj_ba66tywb3e/prompt-history [{"id":"phist_jxd9x97ahk","createdAt":1787597928194,"input":[{"type":"text","text":"Reply only with ok.","mentions":[]}]}] grep -n "Skipping malformed prompt history row" ~/.bb-dev/launchers/<instance>/dev.log 111:@bb/server:dev: [11:59:23] WARN: [server] Skipping malformed prompt history row {"entryId":"phist_deg4hty4uq","requestSequence":6,"threadId":"thr_hz6xuhkvky","errorMessage":"[\n {\n \"origin\": \"array\",\n \"code\": \"too_small\",\n \"minimum\": 1,\n \"inclusive\": true,\n \"path\": [],\n \"message\": \"Too small: expected array to have >=1 items\"\n }\n]","errorName":"ZodError"}The API silently omits the[]row and the server warns. Every subsequent read of this project's history (the composer fetches it on open) repeats the warn.
The fork thread's own client/turn/requested event (seq 6, the requestSequence stored on the bad row) carries "initiator": "user", "input": [], "target": {"kind": "thread-start"} — see 2292/repro/fork-event-seq6.json.
4b. Unit test at the exact write path (fails on base)
Save as apps/server/test/services/prompt-history-2292.test.ts and run cd apps/server && pnpm exec vitest run test/services/prompt-history-2292.test.ts. On 494f66526 the assertion at line 86 fails because the stored inputs contain "[]"; with PR #2295 applied it passes.
// Repro for get-bb/bb#2292: a user-initiated thread-start with empty input
// (an idle fork / side-chat tip fork) persists a prompt_history_entries row
// whose input is "[]". The read path rejects that row with zod
// (`z.array(promptInputSchema).min(1)`) and logs a warn on every read.
//
// On base 494f66526 the FIRST assertion fails: a row with input "[]" exists.
import { describe, expect, it, vi } from "vitest";
import {
createConnection,
createProject,
createThread,
migrate,
noopNotifier,
promptHistoryEntries,
upsertHost,
} from "@bb/db";
import {
listProjectPromptHistory,
recordAcceptedPromptHistoryEntry,
} from "../../src/services/prompt-history.js";
import { textInput } from "../helpers/prompt-input.js";
describe("#2292 empty-input prompt history rows", () => {
it("does not persist a row for a user-initiated empty-input thread start", () => {
const db = createConnection(":memory:");
migrate(db);
const host = upsertHost(db, noopNotifier, {
name: "test-host",
type: "persistent",
});
const project = createProject(db, noopNotifier, {
name: "Project A",
source: { type: "local_path", hostId: host.id, path: "/tmp/project-a" },
}).project;
const logger = {
debug: vi.fn(),
error: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
};
// A forked thread is NOT a hierarchy child (parentThreadId stays null),
// so the scope resolver picks "project" for a user-initiated start.
const fork = createThread(db, noopNotifier, {
projectId: project.id,
providerId: "codex",
originKind: "fork",
});
expect(fork.parentThreadId).toBeNull();
// A valid entry that should still be recalled afterwards.
const normal = createThread(db, noopNotifier, {
projectId: project.id,
providerId: "codex",
});
recordAcceptedPromptHistoryEntry(
{ db },
{
thread: normal,
input: textInput("Investigate auth flow"),
initiator: "user",
target: { kind: "thread-start" },
requestSequence: 1,
},
);
// Exactly what requestThreadProvision passes for `bb thread fork <id>`
// without --prompt, or a side-chat launcher fork with no reply anchor.
recordAcceptedPromptHistoryEntry(
{ db },
{
thread: fork,
input: [],
initiator: "user",
target: { kind: "thread-start" },
requestSequence: 1,
},
);
const storedInputs = db
.select({ input: promptHistoryEntries.input })
.from(promptHistoryEntries)
.all()
.map((row) => row.input);
// FAILS on 494f66526: storedInputs contains "[]".
expect(storedInputs).not.toContain("[]");
// Read path: the "[]" row is dropped with a warn on every read.
const history = listProjectPromptHistory(
{ db, logger },
{ projectId: project.id, limit: 50 },
);
expect(history.map((entry) => entry.input)).toEqual([
textInput("Investigate auth flow"),
]);
expect(logger.warn).not.toHaveBeenCalled();
});
});
$ cd apps/server && pnpm exec vitest run test/services/prompt-history-2292.test.ts # at 494f66526
FAIL @bb/server test/services/prompt-history-2292.test.ts > #2292 empty-input prompt history rows > does not persist a row for a user-initiated empty-input thread start
AssertionError: expected [ …(2) ] to not include '[]'
❯ test/services/prompt-history-2292.test.ts:86:30
Test Files 1 failed (1)
Tests 1 failed (1)
Repro files: 2292/repro/ — prompt-history-2292.test.ts, unit-test-base-494f66526.clean.log, db-after-fork.txt, api-project-prompt-history.json, server-log-warn.txt, fork-event-seq6.json, cli-fork-output.json, pr-2295.diff, unit-test-with-pr-2295.log, cli-fork-with-pr-2295.txt, typecheck-with-pr-2295.log. No screenshots: the bug has no visual surface (the app hides empty drafts; the only observable artefacts are the DB row and the log line).
5. Root cause
Mechanism, end to end:
- A fork request may carry no prompt.
forkThreadRequestSchema.inputis.min(1).optional()(threads.ts#L177);createThreadForkFromRequestdefaults it to[](thread-fork.ts#L109-L115). It only marks the start as agent-initiated (startedOnBehalfOf) when there is anagentContextSeedand no visible input (isSeedOnlyIdleFork, #L144-L146). A fork with neither staysinitiator: "user".createThreadRequestSchemaaccepts the empty array becauseoriginKind !== null(#L146-L152). - Forks are not hierarchy children.
createThreadFromRequestsetshierarchyParentThreadId = originKind === null ? requestInput.parentThreadId : undefined(thread-create.ts#L612-L617; comment at #L644: "Forks and side chats are not hierarchy children"). The new thread therefore hasparentThreadId: null— confirmed in the live output. requestThreadProvisionappends theclient/turn/requestedevent and then unconditionally calls the history funnel with the same input (thread-provisioning.ts#L254-L260).recordAcceptedPromptHistoryEntrydecides the scope purely frominitiator,target.kindandparentThreadId(prompt-history.ts#L159-L175): user + thread-start + root →"project". It never looks at the input, and persists it as-is (#L249-L270);createPromptHistoryEntryJSON-stringifies[](db/prompt-history.ts#L51-L75).- Reads parse every row with
z.array(promptInputSchema).min(1)(#L24) insidebuildPromptHistoryEntries, whose catch block drops the row and logs the warn (#L134-L157). The row is never cleaned up, so the warn repeats on every read.
Why this is the real cause and not a read-side bug: the write and read sides disagree on the invariant. The read side encodes "a remembered prompt has at least one item" (which is the right invariant: there is nothing to recall from an empty prompt, and the runtime never even runs a turn for it — thread-lifecycle.ts#L731-L736). The write side treats "a user-initiated thread-start request was accepted" as sufficient, which became false when empty-input forks were introduced. Per AGENTS.md ("validate at the boundary, then pass typed values"), the row should never be written.
Which real surfaces trigger it: bb thread fork <id> without --prompt/--file/--image/--agent-context-seed (fork.ts#L72); SDK threads.fork({sourceThreadId}); the side-chat launcher fork ("no anchor, no seed", side-chat/app.tsx#L345) and any side chat whose anchor equals the source's last message (resolveReplySeedText returns null → no seed, side-chat/server.ts#L201-L213). Side chats that do get a seed start as initiator: "agent" and never write a row. The web app's "fork from message" flow navigates to the composer and submits a real prompt, so it is not affected.
Deeper observation (not this bug): the same funnel records the full input, so a fork that carries both an agentContextSeed and a visible prompt stores the agent-only seed item in project prompt history. promptInputToDraft does not appear to filter on visibility (only threadQueuedMessages.ts does). I did not drive that path; it is a candidate for a separate issue if recall of agent-only text is undesirable.
6. Proposed fix (first principles)
Reject empty input at the single write funnel — exactly what PR #2295 does — so every call site (provisioning, reprovisioning, send, queued flush) is covered and the stored invariant matches the read schema:
// apps/server/src/services/prompt-history.ts, recordAcceptedPromptHistoryEntry
if (args.input.length === 0) {
return false; // no prompt was sent, so there is nothing to recall
}
Keep the read-side skip as defense in depth for rows that already exist. Optionally add a one-off Drizzle migration DELETE FROM prompt_history_entries WHERE input = '[]' so users who already have bad rows (the issue's 11) stop getting a warn on every history read; the rows carry no information, so deleting them cannot lose anything. What could go wrong: nothing observable — the return value is ignored by all callers (grep confirms no caller reads it), new-turn targets already require min(1) input at the API boundary, and the guard does not alter scope resolution. No wire shape changes, so HOST_DAEMON_PROTOCOL_VERSION does not need a bump.
7. PR review
PR #2295 — "fix(server): never persist empty-input prompt history rows" (bradhallett, cross-repo fork, head 32ce011bf)
What it changes: +11 lines in apps/server/src/services/prompt-history.ts adding the args.input.length === 0 → return false guard before scope resolution, and +36 lines in apps/server/test/services/prompt-history.test.ts ("does not persist empty-input turns as prompt history rows") asserting the funnel returns false, the table stays empty, listProjectPromptHistory returns [], and no warn is logged. No other files. Diff saved at 2292/repro/pr-2295.diff.
Root cause or symptom? Root cause. The guard sits at the only production writer, so no path can persist an empty array, and the stored invariant now matches the read schema. Layer is correct (server owns prompt-history policy; nothing crosses the daemon boundary). No casts, no unknown, no accepted-but-ignored fields, no wire change.
What I ran (PR diff applied onto 494f66526 with git apply; applies cleanly):
pnpm exec vitest run test/services/prompt-history.test.ts test/services/prompt-history-2292.test.ts→ 2 files, 9 tests passed, including the PR's new test and my repro (which fails on base). Log:unit-test-with-pr-2295.log.pnpm exec turbo run typecheck --filter=@bb/server→ green. Log:typecheck-with-pr-2295.log.- Live: restarted my dev instance from the patched tree and repeated step 4 of the repro (
bb thread fork thr_kqdsa8qm7t, no prompt). New threadthr_4cbfetdhjwwas created, landed idle, and noprompt_history_entriesrow was written (table still holds exactly the original valid row plus the pre-fix[]row). Output:cli-fork-with-pr-2295.txt. - Mutation check: with the PR's test file applied but the source guard reverted to base, the PR's test fails at
prompt-history.test.ts:512(AssertionError: expected true to be false— the funnel returnedtrueand wrote the row); my repro fails at itsnot.toContain("[]")assertion. Both pass with the guard restored.
Findings:
| Location | Severity | Finding |
|---|---|---|
apps/server/src/services/prompt-history.ts:253-262 | low | No cleanup of rows that already exist. Users who have the bad rows (the issue's 11) will keep getting a WARN Skipping malformed prompt history row on every project-history read indefinitely. A Drizzle migration deleting input = '[]' rows would finish the job; the PR description explicitly chose to leave them. Not blocking, but worth a follow-up. |
apps/server/src/services/prompt-history.ts:253-259 (comment) | nit | The comment says "a side-chat or fork preload starts the provider session before the first message". Seeded side chats never reach this write as initiator: "user" (they are startedOnBehalfOf agent starts and fail the scope check first); only unseeded forks do. Accurate enough for intent; could say "an idle fork with no prompt" to avoid misleading a future reader into thinking seeded side chats were writing rows. |
apps/server/test/services/prompt-history.test.ts:490-525 | nit | The test uses a plain root thread rather than one with originKind: "fork". The scope path is identical (both have parentThreadId: null), so coverage is fine, but creating the thread with originKind: "fork" would document the real trigger. It also does not keep a valid sibling row to prove non-empty input is unaffected; the pre-existing tests cover that. |
| PR metadata | info | mergeStateStatus: UNSTABLE, "no checks reported" — CI has not run because this is a cross-repository PR. A maintainer needs to approve workflows. No reviews yet. |
| PR description | info | Claims verified: "fails before the fix by construction" (true — my equivalent test fails on base), "single write funnel for all four call sites" (true), "no wire, schema, or API changes" (true), "existing coverage retained" (the two named tests exist at lines 309 and 432 and pass). The description's test-run numbers (1923 tests) are from the author's environment and were not re-run in full here. |
Verdict: MERGE (after CI is approved and green). The change is minimal, at the right layer, addresses the root cause, and is verified by a test that fails without it. The only substantive gap is the missing one-off cleanup of already-persisted rows, which can be a follow-up.
8. Related issues
- #2295 — the fix PR reviewed above.
- #2190 (open) — SQLite db silently reverted to a 2-week-old state; the issue explicitly rules it out as a duplicate. Agreed: unrelated.
- #1321 (merged PR) — host-daemon undeliverable-event fix; unrelated.
- #2052 (open) — CLI human output of
thread history/project historyis raw JSON; touches the same history surfaces but a different defect. - Candidate follow-up (not filed): agent-only
agentContextSeeditems are stored in prompt history for forks that also carry a visible prompt (section 5, "deeper observation").
9. Appendix
Commands run (in order)
gh issue view 2292 --json number,title,body,labels,state,createdAt,author,comments gh pr view 2295 && gh pr diff 2295 pnpm install --frozen-lockfile --prefer-offline; pnpm exec turbo run build # 18/18 tasks, cached git checkout 494f66526; git fetch origin main git log --oneline 494f66526..origin/main -- apps/server/src/services/prompt-history.ts apps/server/src/routes apps/server/src/services/threads packages/db/src packages/domain/src # empty grep -rn recordAcceptedPromptHistoryEntry apps/server/src # 4 call sites scripts/bb-dev-app current; scripts/bb-dev-app env curl -s http://localhost:23422/api/v1/hosts curl -s -X POST http://localhost:23422/api/v1/projects ... (qa project on /tmp/bb-2292-qa) pnpm bb:dev thread spawn --project proj_ba66tywb3e --provider codex --permission-mode accept-edits --title "2292 source" --prompt "Reply only with ok." --json cd apps/server && pnpm exec vitest run test/services/prompt-history-2292.test.ts # FAILS on base (expected) sqlite3 -header $DB "select ... from prompt_history_entries" # before fork: 1 row node packages/scripts/dist/commands/run-cli.js thread fork thr_kqdsa8qm7t --workspace reuse --title "2292 idle fork" --json sqlite3 -header $DB "select ... from prompt_history_entries" # after fork: 2 rows, second is [] curl -s http://localhost:23422/api/v1/projects/proj_ba66tywb3e/prompt-history # 1 entry returned curl -s http://localhost:23422/api/v1/threads/thr_hz6xuhkvky/events?limit=50 # seq 6: initiator user, input [] grep -n "Skipping malformed prompt history row" ~/.bb-dev/launchers/<instance>/dev.log gh pr diff 2295 > pr-2295.diff; git apply --check pr-2295.diff; git apply pr-2295.diff cd apps/server && pnpm exec vitest run test/services/prompt-history.test.ts test/services/prompt-history-2292.test.ts # 9 passed pnpm exec turbo run typecheck --filter=@bb/server # green scripts/bb-dev-app current # restart on patched tree node packages/scripts/dist/commands/run-cli.js thread fork thr_kqdsa8qm7t --workspace reuse --title "2292 idle fork with PR" --json sqlite3 -header $DB "select ... from prompt_history_entries" # still 2 rows: no new [] row git checkout -- apps/server/src/services/prompt-history.ts apps/server/test/services/prompt-history.test.ts; rm apps/server/test/services/prompt-history-2292.test.ts pnpm dev:stop; pkill -f wf_846839f8-f8a-63; rm -rf ~/.bb-dev/...-wf_846839f8-f8a-63-d4073e0642a3 /tmp/bb-2292-qa; lsof -nP -iTCP -sTCP:LISTEN | grep -E ":15422|:23422|:31422" # ports free
Thread rows on the dev instance after both forks
id|parent_thread_id|source_thread_id|origin_kind|status thr_kqdsa8qm7t||||idle thr_hz6xuhkvky||thr_kqdsa8qm7t|fork|idle # created at base: wrote the [] row thr_4cbfetdhjw||thr_kqdsa8qm7t|fork|starting→idle # created with PR #2295 applied: no row
Fork thread event types (base run)
1 client/turn/requested (inherited from source) 2 turn/started 3 turn/input/accepted 4 item/completed 5 turn/completed 6 client/turn/requested initiator=user, input=[], target=thread-start ← requestSequence of the bad row 7 client/thread/start 8 thread/identity 9 thread/contextWindowUsage/updated 10 thread/started 11 thread/identity 12 thread/name/updated 13 system/thread-provisioning
Scope resolver (base)
function resolveAcceptedPromptHistoryScope(args) {
if (args.initiator !== "user") return null;
if (args.target.kind !== "thread-start") return "thread";
if (args.thread.parentThreadId !== null) return null; // forks have parentThreadId === null → falls through
return "project";
}
Other logs: dev instance start, restart on patched tree, turbo build.