Repository navigation
Active time differs between analyze:session and analyze:sessions on the same file #91
Copy link
Copy link
Open
Labels
bugSomething isn't workingSomething isn't working
Description
Activity
Дополнение по второй сессии: тот же Claude Code 2.1.296, другая сессия (аудит 2026-10-11). Это проверка Claude — она не засчитывается.
analyze:sessionanalyze:sessionsтокены 35 494 765 35 494 765 ✓ активное время 27 мин (без округления 26,69) 22,46 мин самый длинный ход 20 мин 16,74 мин - Гипотеза из описания подтверждается и на этой сессии. Разница без округления — 4,24 мин, а Σ(последняя − первая строка запроса) по главной цепочке — 4,32 мин. Расхождение объясняется точкой отсчёта; остаток −0,08 мин.
- Расхождение есть с самого начала. Те же числа получаются на коммите мержа feat(cli): wait_loop finding, API-time metrics, longest-turn inventory #23 (
e4e9d97) — там, где появилось обещание «inventory parity with analyze:session is structural». - Есть вторая, независимая причина — компакт. После fix(turn): compaction is not a turn — one numbering everywhere #69
analyze:sessionне начинает новый ход на компакте, а инвентарь начинает. На синтетической сессии самый длинный ход — 21 против 8 мин. Это заведено отдельно: Compaction splits a turn in the inventory and panel but not in analyze:session #97 (эпик Epic: one number on every surface — cross-surface parity (audit follow-ups) #94). Общее определение активного времени из п. 1 «Что сделать» лучше принимать вместе с решением по Compaction splits a turn in the inventory and panel but not in analyze:session #97, иначе после фикса числа разойдутся снова на сессиях с компактом.
Как проверить на своей сессии (один раз, ~1 мин, из корня репозитория)
cat > /tmp/repro-91.mts <<'EOF' import { readFileSync } from 'node:fs'; const R = process.cwd(); // запускать из корня репозитория const F = process.argv[2]; // путь к копии JSONL-сессии const jsonl = await import(`${R}/src/main/utils/jsonl.ts`); const cli = await import(`${R}/src/cli/analyzeSession.ts`); const inv = await import(`${R}/src/cli/sessionInventory.ts`); const ledger = cli.buildLedger(await jsonl.parseJsonlFile(F)); const CAP = 10 * 60_000; let raw = 0; // те же промежутки, что turnActiveMinutes, но без округления по ходам const byTurn = new Map<number, number[]>(); for (const r of ledger.rounds) { const a = byTurn.get(r.turnIndex) ?? []; a.push(r.timestamp.getTime()); byTurn.set(r.turnIndex, a); } for (const ts of byTurn.values()) for (let i = 1; i < ts.length; i++) raw += Math.min(Math.max(ts[i] - ts[i - 1], 0), CAP); const first = new Map<string, number>(), last = new Map<string, number>(); // Σ(последняя − первая строка запроса) for (const l of readFileSync(F, 'utf8').split('\n')) { if (!l.trim()) continue; const e = JSON.parse(l); if (e.type !== 'assistant' || e.isSidechain || !e.requestId) continue; const t = Date.parse(e.timestamp); if (!first.has(e.requestId)) first.set(e.requestId, t); last.set(e.requestId, t); } let gen = 0; for (const [k, t0] of first) gen += last.get(k)! - t0; const r = await inv.scanSessionFile(F); const m = (ms: number) => +(ms / 60000).toFixed(2); console.table({ 'analyze:session, мин (сумма округлённых ходов)': ledger.activeMinutes, 'analyze:session, мин (без округления)': m(raw), 'analyze:sessions, мин': m(r.activeMs), 'разница без округления, мин': m(raw - r.activeMs), 'Σ(последняя − первая строка запроса), мин': m(gen), }); EOF pnpm exec tsx /tmp/repro-91.mts ~/.claude/projects/<dir>/<session>.jsonl
Если «разница без округления» почти равна «Σ(последняя − первая строка запроса)», гипотеза подтверждена. Живая сессия растёт между запусками, поэтому лучше дать путь к копии файла.
Generated by Claude Code
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working
Наблюдение (одна сессия, один и тот же файл)
analyze:sessionanalyze:sessionsКомментарий в
src/cli/sessionInventory.ts(~строка 233) утверждает «same accounting as turnActiveMinutes».Вероятная причина (гипотеза)
buildLedgerберёт одну строку на запрос — последнюю (keep-last) — и меряет промежутки последняя→последняя: время генерации ответа входит.snapshot) и меряет промежуток до первой строки запроса: время генерации не входит.analyze:sessionокругляет каждый ход отдельно (Math.roundвturnActiveMinutes) и суммирует округлённое.Как воспроизвести
pnpm analyze:session <copy> --json→.ledger.activeMinutes,.ledger.totals.longestTurn.$HOME/.claude/projects/<dir>/временногоHOMEи запуститьHOME=<tmp> pnpm analyze:sessions --json→activeMs,longestTurnMs.requestIdв главной цепочке.Что сделать
sessionInventory.ts.Перепроверка (каждый пункт — дважды, независимо)
Acceptance