fix(auth): scan real session credential paths for fan-out/stale detection

listSessionCredentialPaths scanned <sessions>/<folder>/.claude/.credentials.json,
but real session creds live at
<sessions>/<folder>/services/<serviceId>/.claude/.credentials.json (and under
tasks/<taskId>/...). The mismatch meant writeCredentials' fan-out and
loadFreshestCredentials' stale-copy scan silently missed every real session
file — so old refresh-token copies (family-revocation landmines) were never
overwritten and the session→canonical write-back could not converge dormant
sessions.

Rewrite it via the pure, tested collectSessionCredentialPaths that walks the
actual services/<id>/.claude and services/<id>/tasks/<id>/.claude layout.
Verified on live data: now matches all 24 real session credential files (was 0).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-08-22 10:31:42 +09:00
parent d8abcf0621
commit 04cfe97a14
2 changed files with 72 additions and 8 deletions

View File

@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest';
import {
applyUpdatedTokensToEnvContent,
collectSessionCredentialPaths,
pickFreshestOAuth,
shouldAdoptSessionOAuth,
shouldStartTokenRefreshLoop,
@@ -134,3 +135,31 @@ describe('shouldAdoptSessionOAuth (session→canonical write-back)', () => {
).toBe(false);
});
});
describe('collectSessionCredentialPaths (real session layout)', () => {
it('builds service group + task credential paths, not <group>/.claude', () => {
const tree: Record<string, string[]> = {
'/s': ['iger', 'javis_bot'],
'/s/iger/services': ['claude'],
'/s/iger/services/claude/tasks': ['task-1'],
'/s/javis_bot/services': ['claude'],
'/s/javis_bot/services/claude/tasks': [],
};
const paths = collectSessionCredentialPaths('/s', (dir) => tree[dir] ?? []);
expect(paths).toContain(
'/s/iger/services/claude/.claude/.credentials.json',
);
expect(paths).toContain(
'/s/iger/services/claude/tasks/task-1/.claude/.credentials.json',
);
expect(paths).toContain(
'/s/javis_bot/services/claude/.claude/.credentials.json',
);
// Must NOT use the old broken <group>/.claude path that matched nothing.
expect(paths).not.toContain('/s/iger/.claude/.credentials.json');
});
it('returns nothing when there are no session folders', () => {
expect(collectSessionCredentialPaths('/s', () => [])).toEqual([]);
});
});