Skip to content

fix(opencode): decouple title generation from step counter, add bounded retries (#138) - #153

Merged
jeonghun-jj-lee merged 3 commits into
local/amicodefrom
fix/138-session-title-generation
Aug 9, 2026
Merged

fix(opencode): decouple title generation from step counter, add bounded retries (#138)#153
jeonghun-jj-lee merged 3 commits into
local/amicodefrom
fix/138-session-title-generation

Conversation

@jeonghun-jj-lee

Copy link
Copy Markdown
Contributor

Summary

Every new session received the static title "New session - [timestamp]" and never updated — the chat history became a wall of identical titles.

Root Cause

Title generation in prompt.ts fires exactly once, gated on step === 1. Multiple code paths silently prevent this from ever succeeding:

  1. Silent-turn guard (line 1208) does step++; continue before the title trigger is reached
  2. Prose-question guard (line 1265) has the same step++; continue pattern
  3. Single-message guard inside title() (line 208) bails if userMessages.length !== 1
  4. No retry — title generation is attempted at most once; Effect.ignore swallows all errors

Fix

Two changes in packages/opencode/src/session/prompt.ts:

1. Replace step === 1 with a dedicated titleAttempts counter

// At initialization
let titleAttempts = 0

// At the trigger point (replacing `if (step === 1)`)
if (Session.isDefaultTitle(session.title) && titleAttempts < 3) {
  titleAttempts++
  yield* title({ ... }).pipe(Effect.ignore, Effect.forkIn(scope))
}

The counter is checked every loop pass, so retries fire immediately without waiting for a new user message. The isDefaultTitle check prevents wasted calls after success.

2. Relax the single-message guard in title()

// Before:
if (input.history.filter(real).length !== 1) return

// After:
if (input.history.filter(real).length < 1) return

Allows retries to succeed even after the second user message arrives. Double-titling prevented by the isDefaultTitle check at the caller.

Invariants preserved

  • Effect.ignore stays — title generation remains non-critical and silent
  • Model selection unchanged (getSmallModel → fallback)
  • Title generation remains forked (never blocks the main response)
  • Maximum 3 attempts is a hard cap
  • The <think> tag stripping and 100-char truncation are untouched

Fixes #138

…ed retries (#138)

Title generation fired at most once (gated on `step === 1`), but multiple
code paths (silent-turn guard, prose-question guard) pre-increment `step`
with `step++; continue` before the title trigger is reached. By the time
the loop hits the trigger, step is already past 1 — permanently dead.

Additionally, `title()` itself bailed if `userMessages.length !== 1`,
so even a late retry after a second user message would never succeed.

Fix:
1. Replace `step === 1` with a dedicated `titleAttempts` counter (max 3).
   Checked every loop pass via `Session.isDefaultTitle(session.title)` —
   stops retrying after success (no wasted API calls).
2. Relax the single-message guard from `!== 1` to `< 1` — allows retries
   to succeed even after a second user message arrives.

Invariants preserved:
- Effect.ignore stays (non-critical, silent on failure)
- Model selection unchanged (getSmallModel → fallback)
- Title generation remains forked (never blocks the main response)
- The <think> tag stripping and 100-char truncation are untouched

Fixes #138
@jeonghun-jj-lee
jeonghun-jj-lee force-pushed the fix/138-session-title-generation branch from 41f114e to e03108e Compare August 9, 2026 01:11
Effect.orDie in the title function promoted provider errors (rate limit,
auth, network) into defects. The fork site used Effect.ignore, which only
catches expected errors — defects bypass it, killing the background fiber
with no log and no title set.

Fix:
1. Remove Effect.orDie from the title LLM stream — errors stay in the E
   channel where Effect.ignore handles them gracefully. On failure the
   function exits early (no title), retries on the next loop pass (up to 3).
2. Fix setArchived typecheck: get(sessionID) returns Effect<Info, NotFound>
   but the interface declares Effect<void> — add .pipe(Effect.orDie) since
   archiving a non-existent session is a programmer error.
3. Add integration test confirming title generation fires on a new session.

Fixes #138
@jeonghun-jj-lee
jeonghun-jj-lee force-pushed the fix/138-session-title-generation branch from 7f6c199 to 0d4e25e Compare August 9, 2026 02:26
…#138)

The titlebar SessionChatsDropdown queried sessions from
serverSync().data.path.directory (the server's cwd), which is the
opencode-project workspace dir — not where sessions live. The store for
that directory was never populated, so the dropdown was always empty.

Fix: source sessions from all registered project directories via
globalCtx.ensureServerCtx (the same data path the dashboard uses).
Archive/unarchive handlers now reload the specific session's directory
instead of the stale cwd reference.
@jeonghun-jj-lee
jeonghun-jj-lee merged commit b7ba12a into local/amicode Aug 9, 2026
1 check passed
@jeonghun-jj-lee
jeonghun-jj-lee deleted the fix/138-session-title-generation branch August 9, 2026 02:43
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.

BUG: All new sessions get default title "New session - []" — chat history is indiscernible

1 participant