Skip to content

[codex] restore Codex session history#536

Open
dsus4wang wants to merge 2 commits intotiann:mainfrom
dsus4wang:codex/restore-codex-session-history
Open

[codex] restore Codex session history#536
dsus4wang wants to merge 2 commits intotiann:mainfrom
dsus4wang:codex/restore-codex-session-history

Conversation

@dsus4wang
Copy link
Copy Markdown

Summary

Restores existing Codex sessions with transcript history instead of starting with an empty chat when resuming from the web session picker.

Changes

  • Imports Codex transcript messages when resuming with --hapi-import-history.
  • Restores Codex session id, title, model, reasoning effort, and latest usage metadata from transcript data.
  • Improves Codex session title lookup from local index/thread state.

Validation

  • bun run test:cli -- src/codex/importHistory.test.ts src/modules/common/codexSessions.test.ts src/codex/utils/codexSessionScanner.test.ts src/commands/codex.test.ts src/runner/buildCliArgs.test.ts src/codex/utils/codexUsage.test.ts
  • bun typecheck

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Findings

  • [Major] Local Codex resume replays already-imported transcript history — runCodex imports the transcript before starting the loop when --hapi-import-history is set, but the local launcher also passes that same flag to the scanner. Terminal hapi codex resume ... --hapi-import-history, and web resumes that later switch to local mode, will emit the persisted transcript into the HAPI session a second time. Evidence cli/src/codex/runCodex.ts:76, cli/src/codex/codexLocalLauncher.ts:73.
    Suggested fix:
    const createdScanner = await createCodexSessionScanner({
        transcriptPath,
        replayExistingEvents: false,
        onSessionId: (sessionId) => {
            session.onSessionFound(sessionId);
        },
        onEvent: (event) => {
            // only newly appended transcript events are forwarded
        }
    });
  • [Major] Session picker can resume Codex history in the wrong working directory — the selected Codex session id is sent with whatever trimmedDirectory currently contains, even though the selected session summary already carries its original path. That can restore a thread from repo A while running future tools in repo B. Evidence web/src/components/NewSession/index.tsx:310.
    Suggested fix:
    const handleCodexSessionSelect = useCallback((sessionId: string) => {
        setSelectedCodexSessionId(sessionId)
        const selected = codexSessionsState.sessions.find((session) => session.id === sessionId)
        if (selected?.path) {
            setDirectory(selected.path)
        }
    }, [codexSessionsState.sessions])
    
    // ...
    onSelectSession={handleCodexSessionSelect}

Summary

  • Review mode: initial
  • Found two correctness regressions in the new Codex history restore flow: duplicate imported messages during local replay, and possible cwd/session mismatch from the web picker.

Testing

  • Not run (automation). Suggested coverage: a local Codex resume with --hapi-import-history asserting no duplicate imported messages when the scanner starts, and a web NewSession selection test asserting selected Codex session path populates the spawn directory.

HAPI Bot

}
const createdScanner = await createCodexSessionScanner({
transcriptPath,
replayExistingEvents: session.importHistory,
Copy link
Copy Markdown

@github-actions github-actions Bot Apr 27, 2026

Choose a reason for hiding this comment

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

[MAJOR] runCodex already imports the transcript before the loop starts when --hapi-import-history is set, so passing the same flag into the local scanner makes it replay the same persisted JSONL again. This duplicates the restored conversation for terminal resumes, and also after a web-restored session switches to local mode.

Suggested fix:

const createdScanner = await createCodexSessionScanner({
    transcriptPath,
    replayExistingEvents: false,
    onSessionId: (sessionId) => {
        session.onSessionFound(sessionId);
    },
    onEvent: (event) => {
        // forward newly appended transcript events only
    }
});

sessionType,
worktreeName: sessionType === 'worktree' ? (worktreeName.trim() || undefined) : undefined
worktreeName: sessionType === 'worktree' ? (worktreeName.trim() || undefined) : undefined,
resumeSessionId: agent === 'codex' && selectedCodexSessionId ? selectedCodexSessionId : undefined,
Copy link
Copy Markdown

@github-actions github-actions Bot Apr 27, 2026

Choose a reason for hiding this comment

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

[MAJOR] Selecting a Codex session only sets resumeSessionId; the spawn still uses the current trimmedDirectory. Since the selected session summary includes its original path, this can restore a thread from one repo while future Codex turns run in another repo.

Suggested fix:

const handleCodexSessionSelect = useCallback((sessionId: string) => {
    setSelectedCodexSessionId(sessionId)
    const selected = codexSessionsState.sessions.find((session) => session.id === sessionId)
    if (selected?.path) {
        setDirectory(selected.path)
    }
}, [codexSessionsState.sessions])

// ...
onSelectSession={handleCodexSessionSelect}

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.

1 participant