mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-02 13:39:41 +02:00
fix(unified-sessions): stop the firstPrompt workingDir backfill from cross-contaminating history rows
COD-140's backfill was meant to cover live/persisted rows whose Codeman id doesn't match an on-disk transcript UUID, guessing from the newest transcript in the same workingDir as a last resort. It was also firing for pure history rows whose OWN transcript scan already ran (and genuinely found nothing, e.g. an oversized first message) -- those got silently backfilled with the newest OTHER session's opening line from the same directory. Not a blank row, but actively wrong: old sessions displayed a completely unrelated (often today's live) conversation's first prompt as if it were their own. Skip the workingDir guess for any item that already has its own 'history' source -- it already had a real, direct attempt. Rows with no history source at all (their transcript isn't linked/scanned under their own id yet) still get the guess, matching the original intent. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e888c65c52
commit
a2aed38073
@@ -226,6 +226,16 @@ export function mergeUnifiedSessions(sources: UnifiedSources): UnifiedSessionIte
|
||||
// different UUID. Backfill from the already-passed history: first try the claudeSessionId
|
||||
// join, then the newest transcript in the same workingDir. Never overwrite a non-empty
|
||||
// firstPrompt (so rows keyed to their own transcript are untouched).
|
||||
//
|
||||
// The workingDir guess is a last resort and MUST be skipped for any item that
|
||||
// already has its own 'history' entry (step 1 above already gave it a real,
|
||||
// direct scan of its own transcript). Without this guard, a history row whose
|
||||
// OWN extraction genuinely failed (oversized first message, etc.) silently
|
||||
// inherited the newest OTHER session's opening line from the same directory —
|
||||
// not a blank, but actively wrong: old sessions displayed today's conversation
|
||||
// as if it were their own. A row with no 'history' source at all (its
|
||||
// transcript hasn't been linked/scanned under its own id yet) has no such
|
||||
// direct attempt to prefer, so the guess remains a reasonable stand-in there.
|
||||
const firstPromptByUuid = new Map<string, string>();
|
||||
const firstPromptByWorkingDir = new Map<string, { prompt: string; ms: number }>();
|
||||
// COD-145: lastPrompt rides the same backfill (build parallel indexes; never overwrite).
|
||||
@@ -254,12 +264,13 @@ export function mergeUnifiedSessions(sources: UnifiedSources): UnifiedSessionIte
|
||||
}
|
||||
}
|
||||
for (const item of map.values()) {
|
||||
const hasOwnHistoryEntry = item.sources.includes('history');
|
||||
if (!item.firstPrompt) {
|
||||
// never overwrite an existing non-empty prompt
|
||||
const byUuid = item.claudeSessionId ? firstPromptByUuid.get(item.claudeSessionId) : undefined;
|
||||
if (byUuid) {
|
||||
item.firstPrompt = byUuid;
|
||||
} else if (item.workingDir) {
|
||||
} else if (item.workingDir && !hasOwnHistoryEntry) {
|
||||
const byDir = firstPromptByWorkingDir.get(item.workingDir);
|
||||
if (byDir) item.firstPrompt = byDir.prompt;
|
||||
}
|
||||
@@ -268,7 +279,7 @@ export function mergeUnifiedSessions(sources: UnifiedSources): UnifiedSessionIte
|
||||
const byUuid = item.claudeSessionId ? lastPromptByUuid.get(item.claudeSessionId) : undefined;
|
||||
if (byUuid) {
|
||||
item.lastPrompt = byUuid;
|
||||
} else if (item.workingDir) {
|
||||
} else if (item.workingDir && !hasOwnHistoryEntry) {
|
||||
const byDir = lastPromptByWorkingDir.get(item.workingDir);
|
||||
if (byDir) item.lastPrompt = byDir.prompt;
|
||||
}
|
||||
|
||||
@@ -270,6 +270,39 @@ describe('mergeUnifiedSessions', () => {
|
||||
expect(live!.firstPrompt).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does NOT borrow a sibling transcript for a history-only row whose own extraction failed (no cross-contamination)', () => {
|
||||
// A pure history row already got its own real scan (step 1 keys it under its
|
||||
// OWN sessionId) — if that extraction genuinely failed (oversized first
|
||||
// message, noise-filtered, etc.), the workingDir guess must not paper over
|
||||
// it with an unrelated session's opening line. Regression: an old session
|
||||
// in a shared workingDir was displaying TODAY's live session's firstPrompt
|
||||
// as its own, because the guess didn't check whether this row already had
|
||||
// its own (failed) attempt.
|
||||
const merged = mergeUnifiedSessions({
|
||||
history: [
|
||||
// This session's own transcript scan found no usable prompt.
|
||||
{
|
||||
sessionId: 'old-uuid',
|
||||
workingDir: '/shared',
|
||||
sizeBytes: 5000,
|
||||
lastModified: '2026-01-01T00:00:00.000Z',
|
||||
firstPrompt: undefined,
|
||||
},
|
||||
// A much newer, unrelated session in the same directory.
|
||||
{
|
||||
sessionId: 'newer-uuid',
|
||||
workingDir: '/shared',
|
||||
sizeBytes: 6000,
|
||||
lastModified: '2026-06-01T00:00:00.000Z',
|
||||
firstPrompt: "today's real prompt",
|
||||
},
|
||||
],
|
||||
});
|
||||
const old = merged.find((m) => m.sessionId === 'old-uuid');
|
||||
expect(old).toBeDefined();
|
||||
expect(old!.firstPrompt).toBeUndefined();
|
||||
});
|
||||
|
||||
// COD-145: lastPrompt backfill — mirrors the COD-140 firstPrompt path so the
|
||||
// most-recent user prompt also reaches live rows whose id ≠ transcript UUID.
|
||||
it('backfills lastPrompt onto a live session by claudeSessionId join (uuid-join)', () => {
|
||||
|
||||
Reference in New Issue
Block a user