@@ -259,13 +259,19 @@ export async function updateClaudeSessionId(executorId: string, sessionId: strin
259259
260260/**
261261 * Identity passed to the JSONL fallback scanner. Mirrors the canonical
262- * `(team, custom_name)` columns on `agents`. Both fields must be non-null
263- * to attempt a match — the fallback refuses to return another agent's
264- * transcript when ownership is unknown.
262+ * `(team, custom_name)` columns on `agents`. `customName` is required (the
263+ * scanner refuses to return another agent's transcript when role identity
264+ * is unknown). `team` may be null for legacy / orphan rows where no team
265+ * was ever recorded — in that case the scanner matches on `agentName` alone
266+ * and additionally accepts JSONL bodies whose `teamName` is null.
265267 */
266268export interface ResumeFallbackIdentity {
267- /** Agent's `team` column. Populated by `findOrCreateAgent`. */
268- team : string ;
269+ /**
270+ * Agent's `team` column. Populated by `findOrCreateAgent`. May be null
271+ * for legacy/orphan rows; when null, the scanner relaxes to a customName
272+ * match only.
273+ */
274+ team : string | null ;
269275 /** Agent's `custom_name` column. The role-or-name part of the identity. */
270276 customName : string ;
271277}
@@ -432,7 +438,12 @@ async function defaultScanForSession(cwd: string, identity: ResumeFallbackIdenti
432438
433439 for ( const candidate of sorted ) {
434440 const { teamName, agentName } = await readJsonlIdentity ( candidate . full ) ;
435- if ( teamName !== identity . team || agentName !== identity . customName ) continue ;
441+ if ( agentName !== identity . customName ) continue ;
442+ // Identity.team is allowed to be null for orphan / legacy rows
443+ // (Felipe directive 2026-05-07): in that case match on agentName alone.
444+ // When identity.team is set, require strict teamName equality so we
445+ // don't attach team A's runtime to team B's transcript.
446+ if ( identity . team !== null && teamName !== identity . team ) continue ;
436447 return candidate . name . replace ( / \. j s o n l $ / , '' ) ;
437448 }
438449 return null ;
@@ -467,21 +478,23 @@ type ResumeRow = {
467478/**
468479 * Try the JSONL on-disk fallback for an agent whose DB resume read missed.
469480 * Returns the recovered session UUID if a matching JSONL is found, or null
470- * if no cwd / no identity / no JSONL match. Emits `resume.recovered_via_jsonl`
471- * on hit.
481+ * if no cwd / no customName / no JSONL match. Emits
482+ * `resume.recovered_via_jsonl` on hit.
472483 *
473- * Identity is `(team, custom_name)` and BOTH must be present — a missing
474- * identity makes ownership unverifiable, and we refuse to attach an agent's
475- * runtime to another agent's transcript.
484+ * Identity is `(team, custom_name)`. `custom_name` is required (the scanner
485+ * refuses to attach without role identity), but `team` may be null —
486+ * legacy/orphan rows that never got a team assigned still resume off-disk
487+ * by matching on `agentName` alone (Felipe directive 2026-05-07: previous
488+ * strict-both check stranded rows where team got dropped).
476489 */
477490async function tryJsonlFallback ( agentId : string , row : ResumeRow | null , actor : string ) : Promise < string | null > {
478491 const cwd = row ?. repo_path ?? null ;
479492 if ( ! cwd ) return null ;
480493
481494 const team = row ?. team ?? null ;
482495 const customName = row ?. custom_name ?? null ;
483- const identity : ResumeFallbackIdentity | null = team && customName ? { team , customName } : null ;
484- if ( ! identity ) return null ;
496+ if ( ! customName ) return null ;
497+ const identity : ResumeFallbackIdentity = { team , customName } ;
485498
486499 const scanner = _resumeJsonlScannerDeps . scanForSession ?? defaultScanForSession ;
487500 const recoveredSessionId = await scanner ( cwd , identity ) ;
0 commit comments