Skip to content

fix(gateway): rotate already-stale generated transcript filename on /reset#93496

Merged
vincentkoc merged 2 commits into
openclaw:mainfrom
harjothkhara:fix/reset-rotate-stale-transcript
Jun 16, 2026
Merged

fix(gateway): rotate already-stale generated transcript filename on /reset#93496
vincentkoc merged 2 commits into
openclaw:mainfrom
harjothkhara:fix/reset-rotate-stale-transcript

Conversation

@harjothkhara

Copy link
Copy Markdown
Contributor

AI-assisted PR. Implemented and verified with Claude (Claude Code). I have reviewed and understand the change and own the final diff.

Summary

Gateway session /reset mints a new session id but, when the stored sessionFile basename still embeds a generated id that differs from the entry's logical sessionId (the post-upgrade "already-stale" state), keeps the new session writing to the old <stale-id>.jsonl. The old transcript is archived as <stale-id>.jsonl.reset.<ts>, yet the new active transcript reuses the stale filename even though its header carries the new session id.

resolveResetSessionFile rotated the filename via rewriteSessionFileForNewSessionId({ previousSessionId: currentEntry.sessionId }), which only matches when the basename equals the current logical id. This change keys rotation off the file's actual embedded id using the existing extractGeneratedTranscriptSessionId classifier, so already-stale generated names rotate too. Explicit custom transcript paths have no embedded id and are preserved unchanged.

  • Surface: src/gateway/session-reset-service.ts (resolveResetSessionFile); exports extractGeneratedTranscriptSessionId from src/gateway/session-transcript-files.fs.ts.
  • Runtime correctness only — no config / migration / security surface.

Refs #77770

Real behavior proof

Behavior addressed: TUI/Gateway /reset reused a stale generated transcript filename (<old-id>.jsonl) for the new session when the stored sessionFile embedded an id different from the entry's logical sessionId, so the new session id in the transcript header no longer matched its filename.

Real environment tested: macOS (darwin 25.5.0), repo Node toolchain. Drove the production gateway reset entry point performGatewaySessionReset (the exact function sessions.reset / TUI /reset invoke) against a real on-disk session store under a throwaway OPENCLAW_HOME. No mocks, no test harness.

Exact steps or command run after this patch:

  1. Seed a real sessions.json at <state>/agents/main/sessions/ in the post-upgrade stale state: entry agent:main:main = { sessionId: 2222…, sessionFile: <dir>/1111….jsonl }, with the real 1111….jsonl transcript on disk.
  2. Call the real performGatewaySessionReset({ key: "main", reason: "reset", commandSource: "proof:77770" }).
  3. Read back sessions.json and list the sessions dir.

Run as OPENCLAW_HOME=<tmp> node_modules/.bin/tsx driver.mts from the repo root (driver reproduced below).

Evidence after fix:

== BEFORE /reset ==
  logical sessionId : 22222222-2222-4222-8222-222222222222
  active sessionFile: 11111111-1111-4111-8111-111111111111.jsonl   <-- embeds STALE id
== sessions.reset result ==  ok: true
  new sessionId     : 4381e912-5b6f-45bb-a36a-3ef373b3cd34
  new sessionFile   : 4381e912-5b6f-45bb-a36a-3ef373b3cd34.jsonl
== AFTER /reset (on disk) ==
  active sessionFile: 4381e912-5b6f-45bb-a36a-3ef373b3cd34.jsonl
  sessions dir      : [ '11111111-...jsonl.reset.2026-06-16T04-35-34.469Z',
                        '4381e912-...jsonl', 'sessions.json' ]
== VERDICT: ROTATED to new session id (FIXED) ==

Same driver on current main (before this patch):

== sessions.reset result ==  ok: true
  new sessionId     : 525b3ae9-0090-493a-8219-f353b9a38ba2
  new sessionFile   : 11111111-1111-4111-8111-111111111111.jsonl   <-- NOT rotated
== AFTER /reset (on disk) ==
  active sessionFile: 11111111-1111-4111-8111-111111111111.jsonl
== VERDICT: STILL STALE -- BUG ==

Observed result after fix: the new session's active transcript filename matches its new session id (<new-id>.jsonl) and the stale file is archived as <old-id>.jsonl.reset.<ts>. On main the active transcript stays <old-id>.jsonl while the header id advances — reproducing the report.

What was not tested: I drove the gateway reset runtime directly rather than typing /reset in the interactive TUI (the TUI is a thin client issuing exactly this sessions.reset call). Topic/fork stale variants are covered by unit tests only; no live channel was exercised.

Tests and validation

  • pnpm test src/gateway/server.sessions.reset-models.test.ts18 passed, including a new stale-embedded-id rotation case. The new test is red on both gateway shards without the source fix and green with it (verified by reverting the fix).
  • pnpm tsgo:core ✓ · pnpm tsgo:test:src ✓ · pnpm exec oxfmt --check ✓ · oxlint ✓ · core-boundary + session-accessor-boundary guards ✓.

Risk / scope

Bounded runtime fix: one function plus one new export, reusing the existing extractGeneratedTranscriptSessionId classifier (no duplicated grammar). Custom transcript placements are unaffected (covered by the existing ownership-metadata test). No config, migration, or security surface touched.

Proof driver (driver.mts)
import fs from "node:fs";
import path from "node:path";
import { resolveDefaultSessionStorePath } from "<repo>/src/config/sessions/paths.js";
import { performGatewaySessionReset } from "<repo>/src/gateway/session-reset-service.js";

const storePath = resolveDefaultSessionStorePath();
const sessionsDir = path.dirname(storePath);
fs.mkdirSync(sessionsDir, { recursive: true });

const staleFileId = "11111111-1111-4111-8111-111111111111";
const currentId = "22222222-2222-4222-8222-222222222222";
const staleFile = path.join(sessionsDir, `${staleFileId}.jsonl`);
fs.writeFileSync(staleFile, `${JSON.stringify({ type: "session-header", sessionId: currentId })}\n`);
fs.writeFileSync(storePath, JSON.stringify({
  "agent:main:main": { sessionId: currentId, sessionFile: staleFile, updatedAt: Date.now() },
}, null, 2));

const result = await performGatewaySessionReset({ key: "main", reason: "reset", commandSource: "proof:77770" });
// ...read back sessions.json + list sessionsDir, compare basename to `${result.entry.sessionId}.jsonl`

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: S proof: supplied External PR includes structured after-fix real behavior proof. labels Jun 16, 2026
@vincentkoc vincentkoc self-assigned this Jun 16, 2026
@vincentkoc

Copy link
Copy Markdown
Member

Land-ready maintainer review complete.

  • Best-fix judgment: this is the correct Gateway reset-boundary fix. It reuses the generated-transcript classifier, rotates stale generated names, and preserves custom paths plus topic/fork suffixes.
  • Focused proof: node scripts/run-vitest.mjs src/gateway/server.sessions.reset-models.test.ts src/gateway/session-transcript-files.fs.test.ts src/gateway/session-reset-service.test.ts src/gateway/server.sessions.test.ts src/gateway/server.sessions.reset.test.ts src/gateway/server-methods/sessions.test.ts passed (6 files, 362 tests).
  • oxfmt and git diff --check passed; fresh autoreview is clean; exact-head CI is green.
  • Known gap: no manual TUI reset run. Direct Gateway behavior proof covers the changed boundary.

The maintainer prepare gate build passed. Its first local pnpm check attempt hit a Node 26 heap OOM in the database-first guard; I am rerunning that infrastructure gate with an explicit heap limit before merge.

@vincentkoc
vincentkoc force-pushed the fix/reset-rotate-stale-transcript branch 2 times, most recently from 10f9768 to 09eddec Compare June 16, 2026 06:31
harjothkhara and others added 2 commits June 16, 2026 14:32
…reset

Gateway /reset rotated the transcript filename keyed by previousSessionId =
currentEntry.sessionId, so a post-upgrade sessionFile whose basename already
embeds a different generated id stayed stale: the new session kept writing to
<old-id>.jsonl while its header carried the new id. Key rotation off the file's
embedded id (extractGeneratedTranscriptSessionId) so already-stale generated
names rotate too; explicit custom paths have no embedded id and stay preserved.

Refs openclaw#77770

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@vincentkoc
vincentkoc force-pushed the fix/reset-rotate-stale-transcript branch from 09eddec to 6ae356c Compare June 16, 2026 06:32
@vincentkoc
vincentkoc merged commit d7cebdc into openclaw:main Jun 16, 2026
16 checks passed
@vincentkoc

Copy link
Copy Markdown
Member

Merged via squash.

Thanks @harjothkhara!

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 17, 2026
…reset (openclaw#93496)

Merged via squash.

Prepared head SHA: 6ae356c
Co-authored-by: harjothkhara <[email protected]>
Co-authored-by: vincentkoc <[email protected]>
Reviewed-by: @vincentkoc
crh-code pushed a commit to crh-code/openclaw that referenced this pull request Jun 18, 2026
…reset (openclaw#93496)

Merged via squash.

Prepared head SHA: 6ae356c
Co-authored-by: harjothkhara <[email protected]>
Co-authored-by: vincentkoc <[email protected]>
Reviewed-by: @vincentkoc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime proof: supplied External PR includes structured after-fix real behavior proof. size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants