Skip to content

fix(session-ingest): stop agent-generated titles from clobbering user renames - #4852

Merged
jeanduplessis merged 1 commit into
mainfrom
fix/session-title-race-condition
Jul 29, 2026
Merged

fix(session-ingest): stop agent-generated titles from clobbering user renames#4852
jeanduplessis merged 1 commit into
mainfrom
fix/session-title-race-condition

Conversation

@jeanduplessis

Copy link
Copy Markdown
Contributor

Problem

cli_sessions_v2.title is a single unqualified column with no provenance flag, so two independent write paths race:

  • User rename (cliSessionsV2.rename in apps/web/src/routers/cli-sessions-v2-router.ts): a direct, unconditional UPDATE title = $newTitle.
  • Agent-generated title (async, arrives via the sandbox agent → SessionIngestDO → queue consumer → applyMetadataChanges in services/session-ingest/src/ingest/metadata.ts): also an unconditional UPDATE title = $newTitle.

Whichever write lands last wins. If the agent-generated title arrives after a user renamed the session, it silently clobbers the user's chosen title.

Fix (no schema/migration changes)

Sessions are created with title = NULL — there's no default placeholder string; the column is nullable with no default (packages/db/src/schema.ts), and both insert sites (services/session-ingest/src/routes/api.ts, services/session-ingest/src/session-ingest-rpc.ts) simply omit title unless explicitly provided.

applyMetadataChanges now treats NULL as the "still unset" placeholder and only promotes the title away from it:

  • Inside the existing SELECT ... FOR UPDATE row lock (already used to serialize this metadata update), it reads the current title.
  • If title IS NULL, the agent-generated title is applied as before (folded into the same batched UPDATE).
  • If title is no longer NULL — because a user already renamed the session, or an earlier agent-generated write already landed — the title write is dropped from the batch (delete updates.title) and a warning is logged. The rest of the metadata batch (org, git url/branch, platform, status, parent) is unaffected.

cliSessionsV2.rename is unchanged: it remains an unconditional UPDATE, so a user can always overwrite the title regardless of its current value (placeholder or agent-generated).

This mirrors the existing conditional-write pattern already used in this file for organization_id (membership-gated) and parent_session_id (IS DISTINCT FROM re-check), just gated on "is this still the creation placeholder" instead.

Tests

  • services/session-ingest/src/ingest/metadata.test.ts:
    • Agent-generated title is applied when the row is still at the NULL placeholder.
    • Agent-generated title write is skipped (with the rest of the batch still applied) when the user already renamed the session.
    • Warning is logged when the title write is skipped.
  • apps/web/src/routers/cli-sessions-v2-router.test.ts:
    • rename overwrites the title both when it's still the NULL placeholder and when it already holds a previously-written (e.g. agent-generated) title.

Verification

  • npx vitest run src/ingest/metadata.test.ts src/queue-consumer.test.ts (services/session-ingest) — all passing.
  • npx oxlint on changed files — clean.
  • npx tsc --noEmit (services/session-ingest) — clean.
  • npx tsc --noEmit (apps/web) — no errors in the changed test file.
  • Could not run the apps/web Jest integration suite for cli-sessions-v2-router.test.ts in this sandbox (no local/reachable Postgres or Docker); it should be run in CI or with pnpm test:db locally.

Built for Jean Du Plessis by Kilo for Slack

… renames

cli_sessions_v2.title had no provenance flag: a user rename and an
async agent-generated title write both did an unconditional
UPDATE title=..., so whichever landed last won. If the agent's
LLM-generated title arrived after a user renamed the session, it
silently overwrote the user's chosen title.

Sessions are created with title=NULL (no default/placeholder string
is written at creation). applyMetadataChanges now only promotes the
title away from that NULL placeholder: it reads the current title
under the same row lock used for the rest of the metadata update,
and skips the title write (leaving the rest of the batch untouched)
whenever the title is no longer NULL, whether it was set by a user
rename or an earlier agent-generated write.

cliSessionsV2.rename remains an unconditional UPDATE, so a user can
always overwrite the title regardless of its current value.

No schema changes.

Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
@kilo-code-bot

kilo-code-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the title-race fix in applyMetadataChanges (locks the row, only promotes the title away from the NULL placeholder, leaves it alone once a user rename or prior agent write has set it) and its accompanying tests; the logic is correctly placed under the existing SELECT ... FOR UPDATE lock and no bugs were found in the changed lines.

Files Reviewed (3 files)
  • services/session-ingest/src/ingest/metadata.ts
  • services/session-ingest/src/ingest/metadata.test.ts
  • apps/web/src/routers/cli-sessions-v2-router.test.ts

Reviewed by claude-sonnet-5 · Input: 20 · Output: 3.9K · Cached: 412.2K

Review guidance: REVIEW.md from base branch main

@jeanduplessis
jeanduplessis merged commit 2880d6b into main Jul 29, 2026
21 checks passed
@jeanduplessis
jeanduplessis deleted the fix/session-title-race-condition branch July 29, 2026 10:22
eshurakov added a commit that referenced this pull request Aug 4, 2026
…agent-generated titles (#4999)

PR #4852 gated agent-generated title writes on `title IS NULL`, but
cloud-agent-next creates cli_sessions_v2 rows with a non-null default
title (`New session - <ISO timestamp>` via createSessionForCloudAgent),
so the auto-generated title from the ingest pipeline was always dropped
for those sessions.

Reuse the existing isDefaultSessionTitle semantics (NULL or the CLI
default-title pattern) already used by POST /session/:sessionId/title,
extracted into a shared module, so applyMetadataChanges promotes the
title away from either placeholder form while still never overwriting a
user-chosen title.

Co-authored-by: eshurakov <54751+eshurakov@users.noreply.github.com>
Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants