fix(omp): resolve claudeSessionId alias on boot-recovery reattach

Two bugs compounded to break continuation pinning on every real OMP
case (only /tmp-based manual testing happened to work by coincidence):

1. startInteractive() had a second, unconditional claudeSessionId
   assignment after the mux branch that clobbered its correctly
   resolved value back to the session's own id on every mux path.

2. mangleOmpWorkingDir() assumed omp mirrors Claude Code's directory
   naming (home prefix kept), but omp actually strips $HOME first.
   findLatestOmpSessionId() was silently returning null for every
   case under ~/codeman-cases/, so resumeSessionId never resolved for
   any real case dir - only /tmp paths (outside $HOME) worked, which
   is every dir this feature was previously tested against.

Verified live: killed and relaunched the omp-verify server process
mid-session (plain reattach, pane stayed alive) and confirmed
claudeSessionId now resolves to the real omp transcript uuid instead
of the Codeman session's own id.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
timkjr
2026-08-28 11:32:30 -05:00
co-authored by Claude Sonnet 5
parent 54a930c80e
commit ed983f898b
3 changed files with 92 additions and 5 deletions
+6 -1
View File
@@ -2075,7 +2075,12 @@ export class Session extends EventEmitter {
}
// Set claudeSessionId — when resuming, the Claude conversation ID is the resumed one.
this._claudeSessionId = this._resumeSessionId || this.id;
// Mirrors the mux branch above and must not clobber it: this line runs
// unconditionally after both the mux and direct-PTY paths, so it also needs
// the ompConfig fallback or it stomps the mux branch's correctly-resolved
// OMP alias back to this.id on every mux/plain-reattach boot recovery
// (the "third reset point" — see DECISIONS.md).
this._claudeSessionId = this._resumeSessionId || this._ompConfig?.resumeSessionId || this.id;
this._pid = this.ptyProcess.pid;
console.log('[Session] Interactive PTY spawned with PID:', this._pid);
+17 -4
View File
@@ -17,17 +17,30 @@
import { readdirSync, statSync } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { join, sep } from 'node:path';
/** A real OMP session file is `<ISO-ish-timestamp>_<uuid>.jsonl`; only the uuid matters here. */
const OMP_SESSION_FILE_PATTERN = /^.+_([a-zA-Z0-9-]+)\.jsonl$/;
/**
* Mirrors `omp`'s own directory mangling: every path separator becomes a
* dash. Pure so it's unit-testable without touching the filesystem.
* Mirrors `omp`'s own directory mangling. Confirmed empirically against real
* `~/.omp/agent/sessions/` directory names (2026-08-27): unlike Claude Code's
* `~/.claude/projects/*`, which keeps the home prefix (`-home-user-dev-foo`),
* omp collapses a home-relative workingDir to its home-relative remainder
* FIRST (`/home/user/dev/foo` -> `/dev/foo`) and only then dash-replaces
* (`-dev-foo`) — a path outside $HOME (e.g. `/tmp/...`) is dash-replaced as-is.
* Getting this wrong doesn't error, it just silently returns an empty
* directory listing: findLatestOmpSessionId() below then always falls through
* to null, so continuation pinning quietly degrades to omp's own ambiguous
* `--continue` for every case under $HOME (i.e. virtually all real Codeman
* cases) while appearing to work in `/tmp`-based manual testing.
* Pure so it's unit-testable without touching the filesystem.
*/
export function mangleOmpWorkingDir(workingDir: string): string {
return workingDir.replace(/\//g, '-');
const home = homedir();
const relative =
workingDir === home || workingDir.startsWith(home + sep) ? workingDir.slice(home.length) : workingDir;
return relative.replace(/\//g, '-');
}
/** `~/.omp` — no known env override exists (unlike DSH_HOME); revisit if omp adds one. */