From f48aae38127dafdd4424a48577dc8bd73424b28e Mon Sep 17 00:00:00 2001 From: Matt <174058705+asdf8675309@users.noreply.github.com> Date: Thu, 23 Jul 2026 08:37:24 -0400 Subject: [PATCH] AlgoPhase: drop the non-deterministic "most-recent row" slug fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What AlgoPhase.ts picks which work.json session row to update from --slug, $CLAUDE_SESSION_ID, or --uuid. If none of those matched, it fell back to whichever algorithm-mode row was touched most recently. Run two Algorithm sessions at once (two tabs, or two worktrees) and an unidentified call would quietly bump the wrong session's phase. Saw this happen for real. ## Change One file. resolveSlug() now returns null when nothing identifies the session, so main() errors out ("no algorithm-mode session found — pass --slug or --uuid") instead of guessing. Dropped the pickAlgorithmModeRow() helper. resolveSlug is exported so it's unit-testable. ## Why it's safe That fallback only fired when the caller passed no identifier and $CLAUDE_SESSION_ID wasn't set — exactly the case where there's no right answer. Every normal call (hooks, or an explicit --slug/--uuid) is untouched. Worst case now is a clear error telling you what to pass, and it writes nothing. That beats silently corrupting another session. ## Verification Tested on macOS, Bun 1.3.14. | input | old behavior | new behavior | |-------|--------------|--------------| | --slug S | row S | row S (unchanged) | | $CLAUDE_SESSION_ID matches a row | that row | that row (unchanged) | | --uuid U matches a row | that row | that row (unchanged) | | no identifier, 1 algo row alive | bumps that row (maybe wrong) | null -> errors, no write | | no identifier, 2 algo rows alive | bumps most-recent (wrong ~50%) | null -> errors, no write | End-to-end: 'AlgoPhase.ts build' with no identifier and $CLAUDE_SESSION_ID unset prints the error and leaves work.json byte-identical (md5 unchanged). ## Scope Just AlgoPhase.ts. No API or flag changes — the --slug/--uuid/$CLAUDE_SESSION_ID paths don't move. No new dependency. Co-authored-by: Claude --- LifeOS/install/LIFEOS/TOOLS/AlgoPhase.ts | 45 +++++++----------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/LifeOS/install/LIFEOS/TOOLS/AlgoPhase.ts b/LifeOS/install/LIFEOS/TOOLS/AlgoPhase.ts index 3e1e79b422..5cf54d771a 100755 --- a/LifeOS/install/LIFEOS/TOOLS/AlgoPhase.ts +++ b/LifeOS/install/LIFEOS/TOOLS/AlgoPhase.ts @@ -13,11 +13,12 @@ * Phases (case-insensitive): * observe | think | plan | build | execute | verify | learn | complete | starting * - * Slug resolution priority: + * Slug resolution priority (all deterministic — no guessing): * 1. --slug X (explicit) * 2. row whose sessionUUID === CLAUDE_SESSION_ID env var * 3. row whose sessionUUID === --uuid X - * 4. most-recent (by updatedAt) algorithm-mode row in work.json + * (no most-recent-row fallback — an unidentified session errors instead of + * silently bumping an unrelated row; see resolveSlug) * * Output: * On success — prints `OK: ` to stdout, exits 0. @@ -76,11 +77,11 @@ Usage: Phases: observe | think | plan | build | execute | verify | learn | complete | starting -Slug resolution priority: +Slug resolution priority (deterministic only): 1. --slug X 2. row with sessionUUID === \$CLAUDE_SESSION_ID 3. row with sessionUUID === --uuid X - 4. most-recent algorithm-mode row in work.json + (no most-recent-row fallback — unidentified session errors) Examples: bun ~/.claude/LIFEOS/TOOLS/AlgoPhase.ts think @@ -102,22 +103,7 @@ interface Session { modeHistory?: Array<{ mode: string; startedAt: number; endedAt?: number }>; } -function pickAlgorithmModeRow(sessions: Record): string | null { - let best: { slug: string; t: number } | null = null; - for (const [slug, s] of Object.entries(sessions)) { - if (slug === '__pulse_strip') continue; - if (s.phase === 'complete') continue; - const isAlgo = s.currentMode === 'algorithm' - || s.mode === 'starting' - || (s.mode === 'interactive' && s.phase && s.phase !== 'native'); - if (!isAlgo) continue; - const t = new Date(s.updatedAt || s.started || 0).getTime(); - if (!best || t > best.t) best = { slug, t }; - } - return best ? best.slug : null; -} - -function resolveSlug(args: Args, sessions: Record): string | null { +export function resolveSlug(args: Args, sessions: Record): string | null { // 1. explicit --slug if (args.slug) return args.slug in sessions ? args.slug : null; @@ -136,19 +122,12 @@ function resolveSlug(args: Args, sessions: Record): string | nu } } - // 4. most-recent algorithm-mode row — multi-tab hazard. - // If two Algorithm sessions are alive concurrently and the caller passed - // neither --slug, --uuid, nor CLAUDE_SESSION_ID, we'd write to the - // most-recent one — possibly the WRONG one. Emit a stderr warning so this - // ambiguity is visible. Cato 2026-05-24 self-review flagged this. - const fallback = pickAlgorithmModeRow(sessions); - if (fallback) { - process.stderr.write( - `WARN: AlgoPhase falling back to most-recent algorithm-mode row "${fallback}". ` + - `Pass --slug or --uuid (or set CLAUDE_SESSION_ID) when multiple Algorithm sessions are alive.\n` - ); - } - return fallback; + // No deterministic identifier resolved. Do NOT fall back to the most-recent + // algorithm-mode row — that guess silently bumped an unrelated session's phase + // (observed during the LifeOS v6 migration, 2026-07-06). Return null so main() + // errors instead of writing to the wrong row. Callers must pass --slug/--uuid + // or set CLAUDE_SESSION_ID. + return null; } function main(): number {