Skip to content

Fix Orca Codex sessions missing from external history - #8114

Draft
nwparker wants to merge 1 commit into
mainfrom
nwparker/fix-4444
Draft

nwparker wants to merge 1 commit into
mainfrom
nwparker/fix-4444

Conversation

@nwparker

Copy link
Copy Markdown
Contributor

Description

Closes #4444.

Orca redirects Codex to a managed CODEX_HOME, but its session bridge only linked external system history into that managed home. Sessions created by Orca therefore stayed private to the managed history tree. The bridge also ran before a new rollout file was created, so merely reversing the startup scan would not make the current chat visible.

This PR:

  • reconciles managed history into the configured external Codex history before retaining the existing external-to-managed import;
  • uses same-file hardlinks so Orca and external Codex append to one JSONL instead of divergent copies;
  • exports the exact rollout path from live local Codex SessionStart, with UserPromptSubmit as an event-driven retry if creation was not yet observable;
  • preserves both originals on relative-path collisions and rejects transcript paths or target directories that escape through traversal or symlinks;
  • keeps EXDEV/EPERM failures fail-safe: the managed original remains untouched and no misleading partial copy is created;
  • makes the existing WSL launch-time hardlink bridge bidirectional inside the distro; and
  • registers the experimental agent-session.codex-external-history-bridge reliability gate.

Reliability proof

  • Reliability invariant: a valid session created under Orca managed CODEX_HOME becomes discoverable from the configured external history root without overwriting either corpus or creating divergent mutable copies. Motivated by [Bug]: Codex chats started from Orca workspace do not appear in Codex Mac/CLI project history #4444.
  • Material impact: closes the one-way persistence boundary and makes the current local session externally visible from the first authoritative Codex lifecycle event.
  • Product change type: runtime hardening plus an experimental deterministic gate.
  • Performance risk inventory: touches Codex launch/session-start persistence only. It does not touch typing, focus, rendering, resize, PTY output, tab/workspace switching, git scans, or store subscribers.
  • No-regression evidence: live hooks schedule one O(1) exact-file operation off the hook response path; a bounded 1024-entry success cache suppresses later prompt work. Upgrade recovery adds one incremental managed-history walk to the existing coalesced background bridge. No polling, startup await, hidden-pane wake, subprocess, or renderer work was added.
  • Correctness evidence: the registered five-file Vitest command proves shared-inode appends, repeat/race idempotence, collision preservation, path/symlink rejection, custom source homes, EXDEV/EPERM safety, hook timing, and WSL command construction.
  • Provider/platform coverage: local macOS is covered with synthetic filesystem evidence. Linux/Windows host behavior awaits CI. WSL has command-contract coverage but not a live Windows/WSL run. SSH and remote-runtime are unaffected because their Codex history is not redirected into the host managed home. Daemon PTYs use the same main-process host bridge. Mobile/relay is unaffected.
  • Diagnostics: collision, unsafe target, and link failures emit [codex-session-bridge] warnings containing paths and errors, never session contents.
  • Promotion status: partial/experimental; no promotion until cross-platform CI, packaged external-Codex evidence, and soak history exist.
  • Rollback or demotion rule: demote or revert if the deterministic filesystem oracle flakes, either original is overwritten, or a path creates a divergent mutable copy.

Evidence of fix (before + after)

All fixtures are synthetic. No real auth, user history, reporter attachment, or user-provided code was read or copied.

Before

The exact production-boundary regression was run twice before implementation:

pnpm exec vitest run --config config/vitest.config.ts src/main/codex/codex-session-bridge.test.ts -t "exports Orca-created sessions into the external Codex history root"

1 failed, 9 skipped
ENOENT: no such file or directory, open '<synthetic-home>/.codex/sessions/2026/07/10/rollout-orca.jsonl'

After

Exact original reproduction: 1 passed, 9 skipped
Focused reliability gate: 5 files passed, 33 tests passed
Bridge plus runtime-home integration: 6 files passed, 117 tests passed
pnpm run typecheck: 3 projects passed
Touched-file oxlint: passed
pnpm run check:reliability-gates: passed (32 gates)
pnpm run check:max-lines-ratchet: passed (no new bypasses)
git diff --check: passed

Full pnpm run lint passed oxlint, switch exhaustiveness, styled scrollbar, reliability, max-lines, and localization catalog reference checks, then stopped on an unrelated existing Japanese catalog interpolation mismatch for components.native-chat.composer.uploadingAttachments. No localization files are changed here.

ELI5

Orca and normal Codex were keeping chat notebooks in two different filing cabinets. Orca knew how to put normal Codex notebooks into its cabinet, but not how to put Orca-created notebooks back into the normal cabinet. This makes both cabinets point at the same notebook page, so new writing stays visible from either side without copying or overwriting anyone else’s notebook.

User-facing trade-offs

  • Export requires a hardlink-capable shared filesystem. Cross-device or permission-blocked links preserve the Orca chat but cannot expose it externally; the PR deliberately avoids stale copies and symlinks that Codex ignores.
  • Codex still filters project history by the recorded cwd. Orca keeps the truthful worktree path so resuming cannot silently edit the wrong checkout; users opening external Codex at the original repo path may need the worktree path or codex resume --all.
  • Managed session_index.jsonl rename metadata is not synchronized. The transcript becomes externally discoverable, but an Orca-only renamed title may not appear externally.
  • The WSL bridge is bidirectional on the next launch-time reconciliation; same-live-session WSL hook export is not yet covered.
  • Packaged Codex Mac/CLI validation was intentionally not run against real user history. The deterministic production-boundary oracle is covered; live packaged evidence remains a follow-up gate.
  • Independent agent review was attempted but unavailable because the review service returned a service-wide 503; local adversarial review and the listed automated gates were completed.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds safe hardlink export of managed Codex transcripts into system history, with collision, symlink, traversal, failure, and concurrency handling. Hook events now schedule eligible exports with duplicate suppression and retry behavior. Native reconciliation exports managed sessions before bridging system sessions, while legacy migration logic moves to a dedicated module. WSL session bridging becomes bidirectional, and a reliability gate documents the new contract and validation coverage.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it does not follow the repository's required template or include the mandated sections. Rewrite it using the required sections: Summary, Screenshots, Testing, AI Review Report, Security Audit, and Notes.
Docstring Coverage ⚠️ Warning Docstring coverage is 36.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main fix to Orca Codex session visibility in external history.
Linked Issues check ✅ Passed The changes address #4444 by exporting Orca sessions to external history, preserving originals on collisions, and keeping hardlink failures safe.
Out of Scope Changes check ✅ Passed The added reliability gate, hook integration, tests, and WSL bridge updates all align with the stated fix and validation scope.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/main/codex/codex-session-history-hook.ts (1)

36-41: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Wrap the setImmediate callback in a try/catch.

exportManagedCodexSessionToSystemHistory is designed not to throw, but an unexpected error would surface as an unhandled exception in setImmediate, crashing the Electron main process. A defensive catch prevents a history-export bug from taking down the app.

🛡️ Proposed fix: guard against unhandled exceptions in deferred export
   const task = setImmediate(() => {
-    const result = exportManagedCodexSessionToSystemHistory(transcriptPath, systemCodexHomePath)
-    if (result === 'linked' || result === 'already-linked') {
-      rememberExportedManagedSessionPath(exportKey)
+    try {
+      const result = exportManagedCodexSessionToSystemHistory(transcriptPath, systemCodexHomePath)
+      if (result === 'linked' || result === 'already-linked') {
+        rememberExportedManagedSessionPath(exportKey)
+      }
+    } catch (error) {
+      console.warn('[codex-session-bridge] Unexpected error during managed Codex session export:', {
+        transcriptPath,
+        error
+      })
     }
   })

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 732eeda8-bbd9-4133-8fa9-29c28ae30941

📥 Commits

Reviewing files that changed from the base of the PR and between c5669cc and 0875d7f.

📒 Files selected for processing (12)
  • config/reliability-gates.jsonc
  • src/main/codex/codex-session-bridge.test.ts
  • src/main/codex/codex-session-bridge.ts
  • src/main/codex/codex-session-history-hook.test.ts
  • src/main/codex/codex-session-history-hook.ts
  • src/main/codex/codex-session-legacy-copy.ts
  • src/main/codex/codex-session-managed-export-failure.test.ts
  • src/main/codex/codex-session-managed-export.test.ts
  • src/main/codex/codex-session-managed-export.ts
  • src/main/codex/wsl-codex-session-bridge.test.ts
  • src/main/codex/wsl-codex-session-bridge.ts
  • src/main/index.ts

Comment on lines +40 to +46
replacementPath = `${targetPath}.orca-link-${process.pid}-${Date.now()}`
if (!tryLink(sourcePath, replacementPath)) {
return
}
rmSync(targetPath, { force: true })
renameSync(replacementPath, targetPath)
clearLegacyCopiedSessionMarker(relativePath)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant file and nearby call sites.
git ls-files src/main/codex/codex-session-legacy-copy.ts
wc -l src/main/codex/codex-session-legacy-copy.ts
cat -n src/main/codex/codex-session-legacy-copy.ts | sed -n '1,180p'

# Check for any surrounding logic that could affect the rename/remove sequence.
rg -n "rmSync\\(|renameSync\\(|tryLink\\(" src/main/codex -S

Repository: stablyai/orca

Length of output: 1949


🌐 Web query:

Node.js fs.renameSync overwrite existing destination file Windows libuv MOVEFILE_REPLACE_EXISTING documentation

💡 Result:

In Node.js, the fs.rename and fs.renameSync methods are intended to provide a cross-platform way to rename files, and they generally overwrite the destination file if it already exists [1][2]. On Windows, this operation relies on the underlying libuv library, which uses the Windows MoveFileEx API [3]. Technical details regarding this behavior include: 1. Underlying Windows API: libuv uses MoveFileEx with the MOVEFILE_REPLACE_EXISTING flag to implement the rename operation [3]. This flag explicitly instructs the Windows operating system to replace the destination file if it already exists, provided that security and access control requirements are met [4]. 2. Platform Inconsistencies and Reliability: While the intention is to overwrite the existing file, renaming operations on Windows can be notoriously unreliable due to factors such as antivirus software, indexing services, or other processes holding a lock on the file [5][6]. When a conflict occurs, these operations often fail with errors like EPERM, EBUSY, or EACCES, rather than successfully overwriting the file [5][6]. 3. Documentation and Best Practices: Because of these platform-specific reliability issues, the behavior of fs.rename on Windows has been a subject of ongoing discussion within the Node.js and libuv communities [7][1][8][5]. It is widely recommended to avoid relying on fs.rename for atomic replacements or mission-critical operations on Windows [5]. Developers frequently use user-land modules like graceful-fs, which implement retry logic to handle transient errors that occur when the OS-level move operation fails [5][6]. In summary, while fs.renameSync is designed to overwrite an existing destination file on Windows via the MOVEFILE_REPLACE_EXISTING flag, you may encounter EPERM or similar errors if the file is locked or otherwise inaccessible at the moment of the operation [5][3][4].

Citations:


Remove the pre-renameSync delete fs.renameSync already overwrites the destination, so deleting targetPath first only creates a gap where a failed rename can leave the session file missing.

Comment on lines +86 to +91
function prepareSafeSystemTargetDirectory(
systemSessionsRoot: string,
targetDirectory: string
): boolean {
try {
mkdirSync(systemSessionsRoot, { recursive: true })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Check systemSessionsRoot for symlinks after creation.

mkdirSync(systemSessionsRoot, { recursive: true }) follows a pre-existing symlink and succeeds, but the segment walk only checks intermediate directories — not the root itself. If systemSessionsRoot is a symlink to an arbitrary directory, the final canonical check passes because both canonicalRoot and canonicalTargetDirectory resolve through the same symlink, so relative() stays within the resolved root. This allows the hardlink to be created in the symlink target, bypassing the symlink-escape validation the PR intends to enforce.

🔒 Proposed fix: reject symlinked system history root
   try {
     mkdirSync(systemSessionsRoot, { recursive: true })
+    const rootStat = tryLstat(systemSessionsRoot)
+    if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
+      console.warn('[codex-session-bridge] Refusing symlinked system history root:', {
+        systemSessionsRoot
+      })
+      return false
+    }
     const relativeTargetDirectory = relative(systemSessionsRoot, targetDirectory)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function prepareSafeSystemTargetDirectory(
systemSessionsRoot: string,
targetDirectory: string
): boolean {
try {
mkdirSync(systemSessionsRoot, { recursive: true })
function prepareSafeSystemTargetDirectory(
systemSessionsRoot: string,
targetDirectory: string
): boolean {
try {
mkdirSync(systemSessionsRoot, { recursive: true })
const rootStat = tryLstat(systemSessionsRoot)
if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
console.warn('[codex-session-bridge] Refusing symlinked system history root:', {
systemSessionsRoot
})
return false
}

@nwparker
nwparker marked this pull request as draft July 10, 2026 11:20
@nwparker

Copy link
Copy Markdown
Contributor Author

Blocking follow-up from the bug-bash audit: this PR is intentionally draft until both CodeRabbit findings are fixed and revalidated.

The implementation worker was interrupted by the temporarily overloaded agent gateway before it could apply these changes. Existing green CI predates the findings and is not treated as sufficient.

This branch has not been deployed

No deployments
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]: Codex chats started from Orca workspace do not appear in Codex Mac/CLI project history

1 participant