fix(session-ingest): stop agent-generated titles from clobbering user renames - #4852
Merged
Merged
Conversation
… 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>
Contributor
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryReviewed the title-race fix in Files Reviewed (3 files)
Reviewed by claude-sonnet-5 · Input: 20 · Output: 3.9K · Cached: 412.2K Review guidance: REVIEW.md from base branch |
chrarnoldus
approved these changes
Jul 29, 2026
This was referenced Jul 29, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
cli_sessions_v2.titleis a single unqualified column with no provenance flag, so two independent write paths race:cliSessionsV2.renameinapps/web/src/routers/cli-sessions-v2-router.ts): a direct, unconditionalUPDATE title = $newTitle.SessionIngestDO→ queue consumer →applyMetadataChangesinservices/session-ingest/src/ingest/metadata.ts): also an unconditionalUPDATE 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 omittitleunless explicitly provided.applyMetadataChangesnow treatsNULLas the "still unset" placeholder and only promotes the title away from it:SELECT ... FOR UPDATErow lock (already used to serialize this metadata update), it reads the currenttitle.title IS NULL, the agent-generated title is applied as before (folded into the same batchedUPDATE).titleis no longerNULL— 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.renameis unchanged: it remains an unconditionalUPDATE, 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) andparent_session_id(IS DISTINCT FROMre-check), just gated on "is this still the creation placeholder" instead.Tests
services/session-ingest/src/ingest/metadata.test.ts:NULLplaceholder.apps/web/src/routers/cli-sessions-v2-router.test.ts:renameoverwrites the title both when it's still theNULLplaceholder 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 oxlinton changed files — clean.npx tsc --noEmit(services/session-ingest) — clean.npx tsc --noEmit(apps/web) — no errors in the changed test file.apps/webJest integration suite forcli-sessions-v2-router.test.tsin this sandbox (no local/reachable Postgres or Docker); it should be run in CI or withpnpm test:dblocally.Built for Jean Du Plessis by Kilo for Slack