fix: decode dotdir working directories in history session scanning

decodeProjectKey() couldn't recover a dotdir path (e.g. ~/.codeman) from
Claude Code's encoded project-key names: the encoder maps both '/' and
'.' to '-', so the decoder's candidate joins never matched a hidden
directory on disk. It silently fell through to bare $HOME instead,
which corrupted workingDir for any resumed session under a dotdir case
(observed on ~/.codeman itself: history rows and state.json recorded
"/home/timkjr" instead of "/home/timkjr/.codeman").

Add a dot-prefixed candidate to both the backtracking decoder and its
greedy fallback so a leading empty split segment (the signature of a
literal '.' in the original path) is retried as a hidden directory.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
timkjr
2026-08-04 17:11:51 -05:00
co-authored by Claude Sonnet 5
parent 7e357691af
commit f470f3a4e7
2 changed files with 72 additions and 1 deletions
+34 -1
View File
@@ -18,7 +18,7 @@ import Fastify, { type FastifyInstance } from 'fastify';
import fastifyCookie from '@fastify/cookie';
import fastifyMultipart from '@fastify/multipart';
import { join } from 'node:path';
import { mkdtemp, rm } from 'node:fs/promises';
import { mkdtemp, rm, mkdir, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { createMockRouteContext, type MockRouteContext } from '../mocks/index.js';
import { installRouteErrorHandler } from '../../src/web/route-error-handler.js';
@@ -1288,6 +1288,39 @@ describe('session-routes', () => {
const body = JSON.parse(res.body);
expect(body.data.sessions.length).toBeLessThanOrEqual(50);
});
it('decodes a dotdir working directory (e.g. ~/.codeman) instead of falling back to $HOME', async () => {
// Claude Code's project-key encoding maps both '/' and '.' to '-', so
// "/home/x/.dotcase" and "/home/x/dotcase" collapse to the same-looking
// dash run except for a doubled dash. decodeProjectKey() must still
// recover the real (dotdir) path rather than silently falling back to
// bare $HOME (COD bug: 2026-08-01, ~/.codeman resumed sessions got
// workingDir "/home/timkjr" instead of "/home/timkjr/.codeman").
const home = process.env.HOME as string;
const realDir = join(home, '.dotcase');
await mkdir(realDir, { recursive: true });
const projectKey = realDir.replace(/\//g, '-').replace(/\./g, '-');
const projDir = join(home, '.claude', 'projects', projectKey);
await mkdir(projDir, { recursive: true });
const sessionId = '12345678-1234-1234-1234-123456789012';
const transcriptLine = JSON.stringify({ type: 'user', message: { role: 'user', content: 'hello world' } }) + '\n';
// scanProjectDir skips files under 4000 bytes.
const padding = '#'.repeat(4200 - transcriptLine.length);
await writeFile(join(projDir, `${sessionId}.jsonl`), transcriptLine + padding);
const res = await harness.app.inject({
method: 'GET',
url: `/api/history/sessions?projectKey=${projectKey}`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
const row = body.data.sessions.find((s: { sessionId: string }) => s.sessionId === sessionId);
expect(row).toBeDefined();
expect(row.workingDir).toBe(realDir);
expect(row.workingDir).not.toBe(home);
});
});
// ========== POST /api/sessions (with resumeSessionId) ==========