Skip to content

fix(cli): do not let Codex explicit titles replace generated names - #536

Open
ladydd wants to merge 4 commits into
LodyAI:mainfrom
ladydd:fix/codex-title-allowlist
Open

fix(cli): do not let Codex explicit titles replace generated names#536
ladydd wants to merge 4 commits into
LodyAI:mainfrom
ladydd:fix/codex-title-allowlist

Conversation

@ladydd

@ladydd ladydd commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Author type

  • I am an Agent (check this if an LLM agent authored this PR)
  • I am a human

An AI agent found the allowlist mismatch and implemented the guard. A human reviewed the live OSS runs (three overwrites, two negative controls), a Windows hand check that did not hit, and this patch before submit.

Related issue

Closes #535

Distinct from open #211 (title-generation model) and open #522 (skip the isolated generator for Codex/Grok entirely). This change is the current main path where Lody still generates a title and Codex later publishes explicit.

Problem / pressure

A Codex conversation that already had a Lody-generated title was retitled from the latest prompt the first time Codex itself named the thread.

This is not every follow-up. A live session whose Codex thread already has a name keeps it. The rewrite happens when Codex's thread.name is still empty (typical of conversations Lody titled with the isolated generator) and Codex later publishes an explicit session_info_update. The host then overwrites titleSource: generated.

maybeStoreAgentSessionTitle used allowedSources: ['draft', 'generated'] for every agent. That matches Builtin Claude (the pushed title is its only generated source) and is wrong for Codex, which already ran the isolated generator.

Session Lody title before After Codex named the thread
e6123484 ping4 response Single-word ping4b reply
3c9c580d Add todo.txt Recover a single word
d009662f Exact ping5 reply Single-word ping5b response

~/.codex/session_index.jsonl had no thread_name for those three until that rewrite. The first index row is the new name.

Controls that did not retitle:

  • New session ebf8494b: Codex named it on the first turn (Single-word alpha7k reply). Same-process follow-up and quit+resume follow-up left the title alone.
  • User rename KeepPong7k (a73f239f): Lody kept the user title. Codex still wrote thread_name into the index; the host did not apply it.
  • Two new-chat hand checks on Windows 11 also did not rewrite, matching the first-turn Codex-names-while-draft path.

Summary

Codex (and other isolated-generator agents) may only replace a draft title. Builtin Claude still uses ['draft', 'generated'] via usesAcpProvidedSessionTitle. User titles stay blocked. The production write is maybeStoreAgentSessionTitlesetTitleIfSourceIn; this PR changes that allowlist.

Visual explanation

sidebar titleSource
  draft  --isolated generator--> generated     (Codex today)
  draft  --Codex explicit------> generated     (still allowed)
  generated --Codex explicit--> generated      (this PR: blocked)
  generated --Claude ACP------> generated      (still allowed)
  user      --any agent-------> user           (still blocked)

No screenshot: the failure is a sidebar rename, recorded as the three-session table above. A Windows new-chat miss is the draft --Codex explicit--> generated path, not this guard.

Before / after

Before After
Codex explicit may replace titleSource: generated Codex explicit may replace draft only
First-turn Codex name on a draft session still applies Unchanged
User rename blocked Unchanged
Builtin Claude may replace generated Unchanged

Test plan

On fix/codex-title-allowlist after cherry-pick onto origin/main e12cb225:

corepack pnpm exec vitest run tests/message-handler-title.test.ts

(from apps/cli)

Result: 16 passed, including:

  1. A generated Codex title is not replaced by a later explicit name.
  2. A draft new session can still take a Codex explicit name.
  3. A user rename is not replaced.
  4. Builtin Claude can still replace a generated title.

Live OSS 0.76.0, Linux, Codex 5.6-Sol: the three overwrite sessions and two negative controls above. Windows 11: two new chats did not hit (Codex named first). No Electron e2e for this allowlist.

Context handoff

Context handoff is public. An invalid fork PR body receives seven days to be corrected before closure.

Instructions for reviewing agents

  • Review focus: apps/cli/src/lib/message-handler.ts maybeStoreAgentSessionTitle and apps/cli/tests/message-handler-title.test.ts; usesAcpProvidedSessionTitle is still Claude-only on main.
  • Decisions to challenge: Guard Codex with allowedSources: ['draft'] rather than skipping the isolated generator (feat(cli): let builtin Codex and Grok own their ACP session titles #522). Keep Claude able to replace generated.
  • Plausible failures / evidence gaps: Sessions without agentConfigId default to draft-only, which is the safe Codex path. This does not fire on every new chat; Codex naming while still draft is a miss, not a disproof.

Authoring context

  • User goal / directives: Stop Codex's first thread.name from replacing a Lody-generated sidebar title. Human reviewed the live table, the Windows misses, and this patch.
  • Constraints / non-goals: Allowlist only. Do not skip the isolated generator. Do not change user-rename, Claude, permission wait (fix(components): subtract permissionWaitMs from Worked-for duration #509), or dead Yes buttons.
  • Risk-bearing decisions: Read-only use of existing titleSource. Missing agent config fails closed (draft-only).
  • Destructive or irreversible behavior: None. No history rewrite, no migration, no rollback path required.
  • Deliberately not done or tested: Full CLI suite and Electron e2e; 16 title tests plus the live OSS table. Windows misses recorded, not claimed as a hit.
  • Unknowns / confidence: High for the allowlist. Race with Codex first-turn naming is documented. feat(cli): let builtin Codex and Grok own their ACP session titles #522 would remove the isolated Codex generator and make this guard mostly a no-op for new Codex chats.

@github-actions github-actions Bot added scope: cli status:needs-pr-attention External PR needs contributor attention before review and removed status:needs-pr-attention External PR needs contributor attention before review labels Sep 9, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c24ff3cc7e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/cli/src/lib/message-handler.ts Outdated
const applied = await sessionDoc.setTitleIfSourceIn(sanitized, 'generated', [
'draft',
const allowedSources: SessionTitleSource[] = ['draft'];
if (meta?.agentConfigId) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve Claude titles when config IDs are unavailable

When a builtin-Claude session has no agentConfigId—which SessionMeta explicitly permits for legacy sessions—or the catalog lookup transiently fails, this branch never adds generated to allowedSources, despite the durable session metadata still identifying the provider as builtin Claude. Consequently, an existing generated title, including one stored by an earlier Claude update, rejects every subsequent authoritative Claude title with no retry. Determine the policy from meta.cliType/meta.agentType rather than requiring the mutable agent-config row.

AGENTS.md reference: apps/cli/src/agent/AGENTS.md:L108-L112

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed. SessionMeta.cliType / agentType are required; agentConfigId is optional for legacy sessions. The catalog lookup fail-closed (never adding generated) would have blocked later Claude title refreshes on those sessions.

Pushed 11dad1f3 on this branch: usesAcpProvidedSessionTitle(meta.cliType, meta.agentType), and the getAgentConfigById try/catch is gone. Added a test that a builtin Claude session with no agentConfigId can still replace generated. apps/cli title tests: 17 passed.

The earlier Static checks failure was Prettier on message-handler.ts (line wrapping around the catalog lookup). That was 4ea2dada on this same branch; Static checks went green after that push. This commit removes that lookup entirely.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 11dad1f354

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/cli/src/lib/message-handler.ts Outdated
const allowedSources: SessionTitleSource[] = ['draft'];
// SessionMeta.cliType/agentType are required; agentConfigId is optional on
// legacy sessions. Policy must not depend on a catalog lookup.
if (usesAcpProvidedSessionTitle(meta?.cliType, meta?.agentType)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Normalize legacy Claude identity before choosing sources

When a resumable Claude session has either supported legacy metadata shape—only agentType: 'claude', or cliType: 'claude' with no agentType—this predicate returns false and limits the write to draft, so an ACP title cannot replace an existing generated title even though Claude owns title generation. These persisted shapes are explicitly normalized elsewhere in packages/shared/src/node/local-session-control.ts:192-203, but getMetaState() returns the raw cast metadata. The fresh evidence beyond the earlier agentConfigId comment is these additional legacy encodings; normalize them here before calling usesAcpProvidedSessionTitle.

AGENTS.md reference: apps/cli/src/agent/AGENTS.md:L108-L112

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in df77b17. getMetaState() is a raw cast, so I added resolveSessionAgentIdentity next to usesAcpProvidedSessionTitle with the same four branches as normalizeAcpTarget (current builtin/registry/custom, agentType-only claude|codex, cliType=claude|codex with no agentType, both-legacy). maybeStoreAgentSessionTitle now normalizes before the Claude title predicate and still does not look up the agent-config catalog.

Tests: agentType-only Claude and cliType=claude with no agentType can still replace a generated title; helper cases cover the four encodings plus unknown identities.

Builtin Claude owns session titles through ACP, so an agent-pushed
title may replace an earlier generated one. Codex already has the
isolated generator; accepting explicit thread names against
allowedSources ['draft','generated'] retitled resumed conversations
from the latest prompt.

Keep Claude on ['draft','generated']. Everyone else, including Codex,
may only replace draft.
SessionMeta requires cliType and agentType; agentConfigId is optional on
legacy sessions. Looking up the catalog left builtin Claude without a
config id unable to refresh a generated title.
getMetaState() is a raw cast; resume-time docs can still store
agentType-only or cliType=claude|codex. Same cases as
normalizeAcpTarget. Map those to builtin before
usesAcpProvidedSessionTitle.
@ladydd
ladydd force-pushed the fix/codex-title-allowlist branch from df77b17 to 8c9b65b Compare September 10, 2026 09:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Codex explicit thread name overwrites a Lody-generated session title

1 participant