fix(sessions-history): make archive fallback actually read .jsonl.reset.<ts> transcripts#85
Merged
ryan-dyer-sp merged 3 commits intoJun 4, 2026
Conversation
added 2 commits
June 4, 2026 09:14
The async transcript read path (readRecentSessionMessagesAsync, readSessionMessagesAsync, readSessionMessageCountAsync, visitSessionMessagesAsync) all route through findExistingTranscriptPath in session-utils.fs.ts, which only checked live .jsonl candidates. When the resolved key was an archive twin (agent:<id>:archived:<sessionId>), no live candidate existed and the path returned null, so reads silently returned empty even though .jsonl.reset.<ISO-timestamp> was on disk. Only the sync readSessionMessages had an inline archive fallback. chat.history uses the async read path, so sessions_history against archived sessions returned an empty transcript. This blocks the 'resume yesterday' workflow: agents woke up post-rollover with no way to fetch their predecessor's conversation history. Fix: findExistingTranscriptPath now falls back to resolveArchivedTranscriptPaths() when no live candidate exists. Live .jsonl is still preferred when present, so live reads are unaffected. The fallback handles disambiguation across multiple archive twins under the same sessionId by picking the most-recent reset file (matches the canonical-archive-most-recent behaviour that sessions.resolve already uses). Tests in session-transcript-files.fs.archived-history.test.ts: 4 new cases (prefers-live, sessionId-disambiguation across archives in same dir, picks-most-recent when multiple resets exist for one sessionId, returns-empty when nothing on disk) plus 1 flip from negative-assert to positive-assert. Verified live on aster (running bf4 with the same bug shape): the sync read returned 89 messages, the async read returned 0 against the same archive file. Refs: #82 (archive write side, where this began) Refs: #84 (sessions_list key filter, predecessor)
sessions_history takes a single sessionKey and returns one transcript, so the includeArchived flag never had meaning here - the resolver always lands on either a live or archived store entry and (with the previous commit) returns the right transcript either way. The flag was advertised on the schema but never read by execute. Removed from the tool schema. Tool description rewritten to document the now-functional shape: - sessionKey accepts canonical key, archive-twin key, or bare sessionId - archived sessions return their JSONL transcript automatically - Response carries archived: true when the resolved entry is an archive - Canonical 'resume yesterday' recipe: sessions_list key=<canonical> includeArchived=true to discover archive twin sessionIds, then sessions_history sessionKey=<twin sessionId> Schema test updated to assert the field is gone; existing 'archived flag' test now invokes without the removed parameter.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: be6c601131
ℹ️ 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".
`resolveArchivedTranscriptPaths` only matched archived entries whose name
started with `${sessionId}.jsonl.`, so the topic-qualified shape
`<sessionId>-topic-<N>.jsonl.<reset|deleted>.<ts>` was silently skipped.
After a rollover or delete on a topic-qualified session, the archive
fallback in `findExistingTranscriptPath` therefore returned nothing and
`chat.history` / `sessions_history` came back empty for those sessions.
This change extends the scan to also accept the topic-qualified base
shape — `${sessionId}-topic-<N>.jsonl.<reason>.<ts>` — while still
validating archive artifact suffixes via `isSessionArchiveArtifactName`
and parsing timestamps via `parseSessionArchiveTimestamp`. A small
helper, `matchesSessionArchiveBase`, guards the topic prefix so we do
not accidentally match a different sessionId whose canonical id begins
with `${sessionId}-topic-` as a substring.
Tests:
- New unit cases on `resolveArchivedTranscriptPaths`:
- finds `<sessionId>-topic-<N>.jsonl.reset.<ts>` for the requested id
- returns bare + topic-qualified archives sorted timestamp-desc
- does not match topic archives belonging to a different sessionId
- New end-to-end case via `readRecentSessionMessagesAsync` showing
that `findExistingTranscriptPath` now resolves the topic-qualified
archive when only `<sessionId>-topic-<N>.jsonl.reset.<ts>` exists.
Addresses Codex review feedback on PR #85.
9c4cf8b
into
brightfire/sessions-history-archived
78 of 96 checks passed
ryan-dyer-sp
added a commit
that referenced
this pull request
Jun 4, 2026
Three pre-existing lint annotations on brightfire/sessions-history-archived surfaced by pre-push checks after PR #85's CI run: - src/gateway/sessions-resolve.ts:148 — Array#sort() -> Array#toSorted() - src/gateway/sessions-resolve.ts:187 — Array#sort() -> Array#toSorted() - src/config/sessions/archive-entry.test.ts:12 — drop unused loadSessionStore import toSorted is non-mutating (Node 22+ baseline; vash's tier supports it). The filtered arrays were already fresh from Object.entries().filter(), so the behaviour is identical; this is purely a hygiene change to clear the patch branch of lint debt that pre-dates PRs #84/#85. Refs: #85 Co-authored-by: Vash <vash@brightfire.net>
ryan-dyer-sp
added a commit
that referenced
this pull request
Jun 4, 2026
Three pre-existing lint annotations on brightfire/xgw surfaced by pre-push checks during PR #85's build-stable run: - src/gateway/xgw/outbound.ts:68 - src/gateway/server-methods/sessions-xgw.ts:47 - src/gateway/server-methods/sessions-xgw.ts:48 All three are non-negative integer arguments, so slice() and substring() behave identically. Pure hygiene change to clear lint debt that pre-dates the xgw canonical's latest PR (#72). Co-authored-by: Vash <vash@brightfire.net>
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.
Summary
sessions_historyagainst an archived session (rollover/reset/delete twin) returned an empty transcript even though the.jsonl.reset.<ISO-timestamp>file was on disk and valid. This commit fixes the async read path so archive twins return their actual content, and tidies up a parameter that was advertised but never read.Motivation
Enable the "resume yesterday's conversation" workflow. When an OpenClaw session rolls over overnight (lazy reset on first message after the daily boundary), the predecessor session is archived under a twin key. Agents and humans should be able to call
sessions_history sessionKey=<predecessor-sessionId>to load that prior transcript and pick up where they left off. Today that call returns nothing.This sits on top of #82 (archive-write fix) and #84 (sessions_list
keyfilter for lineage discovery). Together those three PRs land the full "rollover + discover + resume" pipeline.Repro (vash, today)
agent:main:mainrolled over at 2026-06-04 07:18:15 CDT. Predecessor sessionId8f5bd13c-418f-4448-9cc8-fd29bcb18620. Transcript file8f5bd13c-...jsonl.reset.2026-06-04T12-18-15.908Zexists on disk (873KB, 371 lines, valid JSONL).The
archived: trueflag is set correctly — the resolver found the right store entry — but the transcript content was never loaded.Verified live on aster too (running an earlier bf-version of the patch): the sync
readSessionMessagesreturns 89 messages, but the asyncreadRecentSessionMessagesAsyncreturns 0 against the same archive file. Same bug shape.Root cause
findExistingTranscriptPathinsrc/gateway/session-utils.fs.tsonly checked live.jsonlcandidates. All four async read paths (readRecentSessionMessagesAsync,readSessionMessagesAsync,readSessionMessageCountAsync,visitSessionMessagesAsync) route through it. When the resolved key is an archive twin, no live candidate exists and the path returns null, so reads silently return empty. Only the syncreadSessionMessageshad an inline archive fallback.chat.history(whichsessions_historyuses) goes through the async path.Changes
Commit 1 —
fix(sessions-history): archive fallback in findExistingTranscriptPathsrc/gateway/session-utils.fs.ts—findExistingTranscriptPathnow falls back toresolveArchivedTranscriptPaths()when no live candidate exists. Live.jsonlis still preferred when present (no behaviour change for live reads).src/gateway/session-transcript-files.fs.archived-history.test.ts— 4 new tests (prefers-live, sessionId-disambiguation across multiple archives in same dir, picks-most-recent-reset when multiple resets exist for one sessionId, returns-empty when nothing on disk) plus 1 case flipped from "documents the bug" to a positive assertion. 18 tests total (was 14).Commit 2 —
refactor(sessions-history): remove unused includeArchived parametersrc/agents/tools/sessions-history-tool.ts— removed theincludeArchivedschema field. Was advertised but never read byexecute.src/agents/tool-description-presets.ts— rewrote thesessions_historytool description to document the now-functional shape:sessionKeyaccepts canonical keys /:archived:<sessionId>keys / bare sessionIds; archive fallback is automatic; response carriesarchived: truewhen reading from an archive; canonical "resume yesterday" recipe viasessions_list→sessions_history.src/agents/tools/sessions-history-tool.test.ts— schema test asserts the removed field is gone; existing "archived flag" case invokes without the removed parameter.Test plan
pnpm check:changedhas one pre-existingtsgo:allfailure insrc/config/sessions/store.pruning.integration.test.ts(missingsessionHistoryRetentionMson a test fixture). Reproduces on the base branch with my changes stashed — not caused by this work. Flagging for a follow-up; not in scope here.Out of scope
BF: Register Patch).Refs
sessions_listkey filter for lineage discoverychat.historyfalls back to archived transcript files (.jsonl.reset.*) when store entry hasarchived: true" — fix makes this actually true on the async path.