ð 52æ¥éã»2340è¡ãã³ã¹ãèšé²ãããã¶ãŒãã ã£ãStop hookã®çœ
æ10äžã®å€§åŠçããå幎ã§ãããŸã§æ¥ãããã®ã¯ãClaudeã«ç°å¢ããšèªåŸãããããã§ãââãããŠãã®ç°å¢ãã52æ¥éã»2340è¡ã«ããã£ãŠã³ã¹ãããã¹ãŠãŒããšèšé²ããªãããäžåºŠãæå¥ãèšããã«åãç¶ããŠããŸããã
ãªããã®ä»çµã¿ãå¹ãã®ã
Claude Codeã䜿ãå§ããå€ãã®äººã¯ãæåã¯ã䟿å©ãªãã£ããAIããšããŠäœ¿ããŸããã§ããæåãäžå®é¡ãè¶ ãããšèãæ¹ãå€ãããŸããAIã«ãæç€ºããŠåãããã®ã§ã¯ãªãããç°å¢ããšèªåŸãããããšããçºæ³ãžã®è»¢æã§ãã
éããå
·äœçã«èšããšãããã§ããåè
ã¯æ¯åèªåãããã³ãããæã£ãŠçµæãããããŸããåŸè
ã¯ãèªåãå¯ãŠããéãhookãèµ°ããã¹ã¯ãªãããå®è¡ããããã°ãç©ãŸããŠããŸãããããã®å Žåãlaunchdã§å®æå®è¡ããããžã§ããåæ°æ¬ãããClaude Code ã® Stop hook ãæ¯ã»ãã·ã§ã³æ«ã«èµ°ããŸããæèµ·ãããšDesktopã«åæ¥ã®ããªãŒããå±ããŠããŠãã©ã®ã»ãã·ã§ã³ãã©ãã ãããŒã¯ã³ãæ¶è²»ãããã ~/.claude/metrics/costs.jsonl ã«èšé²ãããŠããââãããçæ³åœ¢ã§ããã
ãªãã³ã¹ã远跡ãå¿ èŠããšãããšãClaude Code ã®MAXå¥çŽã¯æå®é¡ã§ããã䜿ãããããšç¿æã®ãã£ããå®è³ªçã«è©°ãŸãããšããæèŠçãªäžéããããŸããã©ã®ã»ãã·ã§ã³ã«ãã©ã®ã¢ãã«ã§ãã©ãã ãæ¶è²»ããããå¯èŠåããŠãããªããšãã³ã¹ãæèŠããªãç¶æ ã§èªåŸãšãŒãžã§ã³ããèµ°ããç¶ããããšã«ãªããŸããèªåŸç°å¢ã¯äŸ¿å©ãªã»ã©ã³ã¹ããç¡èªèŠã«é£ããŸããã ããèšæž¬ãå æ±ºãªã®ã§ãã
Stop hook ã¯ãã®èšæž¬ãæ
ãä»çµã¿ã§ããClaude Code ã¯åã»ãã·ã§ã³ãçµäºãããšãïŒãŠãŒã¶ãŒã /exit ãããšããã¿ã€ã ã¢ãŠããããšãïŒã«ãsettings.json ã® Stop ã»ã¯ã·ã§ã³ã«ç»é²ãããã³ãã³ããå®è¡ããŸããããã«ã³ã¹ãéèšã¹ã¯ãªããã眮ãã°ããã»ãã·ã§ã³çµäº = èªåã§èšé²ããšãããã€ãã©ã€ã³ãæç«ããŸãã人éãã³ã¹ããExcelã«æå
¥åãããããªããšã¯äžèŠã«ãªããŸãã
ãåããŠããããšãæ£ããåããŠãããã¯éãââãã®æèŠããšã³ãžãã¢ãªãããããšæããŸãããã°ãåºç¶ããŠããã®ã«äžèº«ãå šéšãŒããšããã®ã¯ããšã©ãŒããå§æ«ãæªãã§ãããšã©ãŒã¯æ°ã¥ããããã§ããããŒãã䞊ã¶ãã°ã¯ãæ£åžžã«èšé²ãããŠããããšããå€èгãä¿ã¡ãªãããå åŽãå®å šã«ç©ºæŽã§ãããããã¯ããã52æ¥éã2340è¡åèŠèœãšããŠããŸããã
ã³ã¹ãéèšããéãã«åãã€ããŠãããæ ¹æ¬åå ã¯ãStop hook ã® stdin ã« usage ã model ãã£ãŒã«ããååšããªããšããåçŽãªäºå®ã§ãããæåã®ããŒãžã§ã³ã¯ãStopããã¯ã® stdin ã«ã¯ããŒã¯ã³æ°ãå
¥ã£ãŠããã¯ãããšããæã蟌ã¿ã§æžãããŠããã®ã§ãã
å šäœã®æµã
çŸåšã® cost-tracker.jsïŒv2ïŒãã©ããããã€ãã©ã€ã³ã§åãããå
ã«ææ¡ããŠãããšãåŸã®è§£èª¬ãå
¥ãããããªããŸãã
Claude Code ã»ãã·ã§ã³
â
â ïŒã»ãã·ã§ã³çµäºã€ãã³ãïŒ
âŒ
Stop hook èµ·å
â
â stdin ã«æµããŠãããã€ããŒã
â { session_id, transcript_path, cwd, hook_event_name, ... }
â â usage / model ã¯ååšããªã â ãããæ§ããŒãžã§ã³ã®ç©Ž
âŒ
cost-tracker.js
â
â transcript_path ãåãåºã
âŒ
ã»ãã·ã§ã³ JSONL ãã¡ã€ã«ïŒClaude Codeãæžãç¶ããŠãããã°ïŒ
â
â è¡ããšã«ããŒã¹
â { type: "assistant", message: { model, usage: { ... } } }
âŒ
sumUsageFromTranscript()
â
â å
šã¢ã·ã¹ã¿ã³ãã¿ãŒã³ãèµ°æ»ããŠ
â input_tokens / output_tokens / cache_* ã环ç©å ç®
âŒ
RATE_TABLE ã§ã¢ãã«å¥ã¬ãŒããæãã
â
â estimatedCostUsd = Σ tokens à rates / 1e6
âŒ
~/.claude/metrics/costs.jsonl ã«1è¡è¿œèš
ãã€ã³ãã¯ãStopããã¯ã§ä¿¡é Œã§ããå¯äžã®ããŒã¿ãœãŒã¹ã¯transcriptãã¡ã€ã«ããšããèšèšææ³ã§ããstdin ãã€ããŒãã«çŽæ¥ usage ãæ¥ãããšãæåŸ ããã®ã§ã¯ãªããClaude Code ãã»ãã·ã§ã³äžã«æžãç¶ããŠãã JSONL ãã¡ã€ã«ãèªåã§èªããããã v2 ãžã®åãæ¿ãã®æ žå¿ã§ãã
ã¢ãã«å¥ã¬ãŒãããŒãã«
ãã¡ã€ã«ã®34ã38è¡ç®ã«å®éã®ã¬ãŒããå®çŸ©ãããŠããŸãã
// Approximate per-1M-token billing rates (USD).
// Cache creation: 1.25x input rate. Cache read: 0.1x input rate.
const RATE_TABLE = {
haiku: { in: 0.80, out: 4.0, cacheWrite: 1.00, cacheRead: 0.08 },
sonnet: { in: 3.00, out: 15.0, cacheWrite: 3.75, cacheRead: 0.30 },
opus: { in: 15.00, out: 75.0, cacheWrite: 18.75, cacheRead: 1.50 }
};
Haikuã»Sonnetã»Opus ã®3段éã§ããgetRates() ã¯ã¢ãã«åã®æååã« 'haiku' 'opus' ãå«ãŸãããã©ããã§æ¯ãåããã©ã¡ãã§ããªããã° Sonnet ãšããŠæ±ããŸããããã¯ã·ã³ãã«ã«æååãããã§ååã§ãæ£èŠè¡šçŸã䜿ãã»ã©ã®ãã®ã§ã¯ãããŸããã
ãã£ãã·ã¥ã¯ã³ã¹ãèšç®ã§æå€ãšå¹ããŠããŸããcache_creation_input_tokensïŒååãã£ãã·ã¥æžã蟌ã¿ïŒã¯éåžžå
¥åã®1.25åãcache_read_input_tokensïŒ2åç®ä»¥éã®èªã¿åºãïŒã¯0.1åã§ããOpus ã§å€§éã«ãã£ãã·ã¥ã䜿ãã°èªã¿åºãå䟡ã¯å®è³ª1.50ãã«/100äžããŒã¯ã³ã§ãHaiku ã®éåžžå
¥åããå®ããªãããšããããŸãã
transcriptãèµ°æ»ãã颿°
57ã90è¡ç®ãä»åã®ä¿®æ£ã®æ žå¿ã§ãã
function sumUsageFromTranscript(transcriptPath) {
let content;
try {
content = fs.readFileSync(transcriptPath, 'utf8');
} catch {
return null; // èªããªããã° null ãè¿ããŠãŒãæ±ãã«ãã
}
let inputTokens = 0;
let outputTokens = 0;
let cacheWriteTokens = 0;
let cacheReadTokens = 0;
let model = 'unknown';
for (const line of content.split('\n')) {
if (!line.trim()) continue;
let entry;
try { entry = JSON.parse(line); } catch { continue; }
if (entry.type !== 'assistant') continue; // user/toolè¡ã¯ã¹ããã
const msg = entry.message;
if (!msg || !msg.usage) continue;
const u = msg.usage;
inputTokens += toNumber(u.input_tokens);
outputTokens += toNumber(u.output_tokens);
cacheWriteTokens += toNumber(u.cache_creation_input_tokens);
cacheReadTokens += toNumber(u.cache_read_input_tokens);
if (msg.model && msg.model !== 'unknown') model = msg.model;
}
return { inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens, model };
}
è¡ããšã« JSON ãããŒã¹ã㊠type === 'assistant' ã®ãšã³ããªã ããåãåºããmessage.usage ã环ç©å ç®ããŸããã¢ãã«åã¯æåŸã«èŠã€ãã£ããã®ã䜿ããŸãïŒã»ãã·ã§ã³éäžã§ã¢ãã«ãåãæ¿ãã£ãŠããæ«å°Ÿã®ã¢ã·ã¹ã¿ã³ãã¿ãŒã³ãæ£ããã¢ãã«ãæã£ãŠãããšããæ³å®ã§ãïŒã
ãšã©ãŒåŠçã¯æå³çã«ããããªã£ãŠããŸãããã¡ã€ã«ãèªããªããã° nullãè¡ãããŒã¹ã§ããªããã° continueãã©ã¡ãã®å Žåã Stop hook å
šäœã倱æãããªãããã«ããŠãããŸããã³ã¹ãèšé²ãæ¬ ããããšãããStop hook ããšã©ãŒãåã㊠Claude Code ã®ã»ãã·ã§ã³çµäºã«åœ±é¿ããæ¹ãåé¡ã ããã§ãã
stdinã®èªã¿åããšãããŒå šäœ
100è¡ç®ããå§ãŸã stdin åŠçã®éšåã確èªããŠãããŸãã
const MAX_STDIN = 64 * 1024;
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
if (raw.length < MAX_STDIN) raw += chunk.substring(0, MAX_STDIN - raw.length);
});
process.stdin.on('end', () => {
try {
const input = raw.trim() ? JSON.parse(raw) : {};
const transcriptPath = (typeof input.transcript_path === 'string' && input.transcript_path)
? input.transcript_path
: process.env.CLAUDE_TRANSCRIPT_PATH || null;
// ...
let usageTotals = null;
if (transcriptPath && fs.existsSync(transcriptPath)) {
usageTotals = sumUsageFromTranscript(transcriptPath);
}
} catch {
// Non-blocking â never fail the Stop hook.
}
process.stdout.write(raw); // ECC hook convention: stdin ããã®ãŸãŸ stdout ã«æµã
});
stdin ããåãåãã®ã¯æå€§64KBã§ããClaude Code ã® Stop hook ãã€ããŒãèªäœã¯æ°çŸãã€ãçšåºŠãªã®ã§ããã§ååã§ãããäžéãèšããŠããããšã§å·šå€§ãªå ¥åãæµã蟌ãã§ãè©°ãŸããªãèšèšã«ããŠãããŸãã
transcript_path ã®åãåºãã¯äºæ®µæ§ãã§ãstdin ã® JSON ã«ããã°ããã䜿ãããªããã°ç°å¢å€æ° CLAUDE_TRANSCRIPT_PATH ã«ãã©ãŒã«ããã¯ããŸããã©ã¡ãããªãå Žå㯠null ã«ãªããusageTotals 㯠null ã®ãŸãŸãªã®ã§ããŒã¯ã³æ°ã¯å
šéš 0 ãšããŠèšé²ãããŸãââããã¯æ§ããŒãžã§ã³ãšåãçµæã§ãããæå³çãªãŒãã§ãããã©ãŒã«ããã¯å
ãååšããããããã¡ã€ã«ãã¹ãåããéåžžã±ãŒã¹ã§ã¯å¿
ã transcript ãèªãŸããŸãã
æåŸã® process.stdout.write(raw) 㯠ECC hook ã®èŠçŽã«åŸã£ããã®ã§ããStop hook 㯠stdin ã stdout ã«æµãããšã§æ¬¡ã®ããã¯ã«ããŒã¿ãæž¡ããããã«ãªã£ãŠããããã®ã¹ã¯ãªããã¯ã³ã¹ãéèšãå¯äœçšãšããŠè¡ããªããããã€ããŒãããã®ãŸãŸééãããŸãã
1ã»ãã·ã§ã³1è¡ã®çޝç©èšèš
ã³ã¡ã³ãã®20ã22è¡ç®ã«æžãããæåãå°å³ã«éèŠã§ãã
// Cumulative behavior: Stop fires per assistant response, not per session.
// Each row therefore represents the cumulative session total up to that point.
// To get per-session cost, take the last row per session_id.
Stop hook ã¯ã¢ã·ã¹ã¿ã³ãã¿ãŒã³ããšã«çºç«ããŸããã€ãŸãã1ã»ãã·ã§ã³ã§10åã¢ã·ã¹ã¿ã³ããå¿çããã° costs.jsonl ã«10è¡è¿œèšãããŸããåè¡ã¯ãã®æç¹ãŸã§ã®ã»ãã·ã§ã³çޝç©ããŒã¯ã³æ°ãªã®ã§ãã1ã»ãã·ã§ã³ã®ã³ã¹ãããåãåºããããã°åã session_id ã®è¡çŸ€ã®ãã¡æåŸã®1è¡ã䜿ããŸããæ¥æ¬¡éèšãªã session_id ã§ã°ã«ãŒãã³ã°ããŠæ«å°Ÿè¡ãåãããããã date ã§ãŸãšãã圢ã«ãªããŸãã
ãã®èšèšã¯ã·ã³ãã«ã§ãããéäžã¯ã©ãã·ã¥ããå Žåã§ãæåŸã«èšé²ãããè¡ãŸã§äœ¿ãããšããèé害æ§ããããŸããã»ãã·ã§ã³ãæ£åžžçµäºããªããŠãããã®æç¹ãŸã§ã®çޝç©ã¯æ®ããŸãã
å®è£ ã®è©³çް
toNumber() ââ ãåãåããªãããé»ã£ãŠåžåãã颿°
ååã§ç޹ä»ãã sumUsageFromTranscript() ã®äžã§ããã¹ãŠã®å ç®ã toNumber(v) ãéã£ãŠããŸãïŒ47ã50è¡ç®ïŒã
function toNumber(v) {
const n = Number(v);
return Number.isFinite(n) ? n : 0;
}
ãã£ã3è¡ã§ãããããããªããã°äºæ
ãèµ·ããŸããClaude Code ã® JSONL ã«ã¯ããã£ãŒã«ããæ¬ èœããŠããè¡ãnull ãå
¥ã£ãŠããè¡ãçšã«ãã£ãã·ã¥é¢é£ãã£ãŒã«ããæååãšããŠå
¥ã£ãŠããè¡ãæ··åšããŸããNumber(null) 㯠0 ã«ãªããŸãããNumber(undefined) ã Number(null) ã NaN + æ£åžžå€ = NaN ã«ãªããŸããäžç®æã§ã NaN ãæ··å
¥ãããšçޝç©åèšå
šäœã NaN ã«ãªããæçµçãªã³ã¹ãæšèšã NaN ãšã㊠JSONL ã«æžãããŸããåŸããéèšã¹ã¯ãªãããæžãããšããNaN ã®è¡ãæ··ãã£ãŠãããšåèšãå£ããŸãã
Number.isFinite() ã䜿ã£ãŠããã®ã¯ isNaN() ããå³å¯ã ããã§ããisNaN(Infinity) 㯠false ãè¿ããŸããã³ã¹ãèšç®ã« Infinity ãå
¥ãã°åèšãå£ããã®ã§ãisFinite ã§ãŸãšããŠåŒŸããŠããŸãã
ãé²åŸ¡çãããããšæãããããããŸããããStop hook ã¯æ¯ã»ãã·ã§ã³æ«ã«ç¡äººã§èµ°ããã®ã§ãã人éããã®éœåºŠç¢ºèªããããã§ã¯ãªãã®ã§ãæ³å®å€ã®å ¥åãåãåã£ããšãã«éãã«ãŒããšããŠæ±ãããã°å šäœãå£ããªãããšã®æ¹ãéèŠã§ãã
session_id ã®äžéãã©ãŒã«ããã¯
108ã112è¡ç®ã® sessionId ã®åãåºãæ¹ãèŠãŠãã ããã
const sessionId =
sanitizeSessionId(input.session_id) ||
sanitizeSessionId(process.env.ECC_SESSION_ID) ||
sanitizeSessionId(process.env.CLAUDE_SESSION_ID) ||
'default';
äžæ®µéã®ãã©ãŒã«ããã¯ããããŸãããªããããªã£ãŠããããšãããšãStop hook ã®å®è¡ç°å¢ãã»ãã·ã§ã³ã®æèã«ãã£ãŠå°ããã€éãããã§ãã
Claude Code ãçŽæ¥èµ·åããã»ãã·ã§ã³ã§ã¯ input.session_id ãæ£åžžã«æ¥ãŸããã§ãããlaunchd ããåŒã°ããã©ãããŒã¹ã¯ãªããçµç±ã§ã»ãã·ã§ã³ãèµ·åããå Žåãããã¹ãç®çã§ hook ãåäœå®è¡ããå Žåã¯ãstdin ãã€ããŒããçç¥ããããå¥åœ¢åŒã«ãªãããšããããŸããECC_SESSION_ID ãš CLAUDE_SESSION_ID ã¯ããããã£ãã±ãŒã¹ã«åããŠç°å¢å€æ°ãšããŠæž¡ããããã«ããŠãããŸãã
sanitizeSessionId() 㯠session-bridge.js ããã€ã³ããŒããã颿°ã§ãè±æ°åãšãã€ãã³ã»ã¢ã³ããŒã¹ã³ã¢ä»¥å€ãé€å»ããŠãã¹çã«å®å
šã«äœ¿ããããã«ãµãã¿ã€ãºããŸããStop hook çµç±ã§ãã¡ã€ã«ã«æžãããã»ãã·ã§ã³ ID ã¯ãã®ãŸãŸ costs.jsonl ã®è¡ã«å
¥ããããæ¹è¡ãã¹ã©ãã·ã¥ãæ··å
¥ãããšåŸã®éèšã¹ã¯ãªãããå£ããŸãã
æåŸã® 'default' ãã©ãŒã«ããã¯ã¯ãã©ããããã»ãã·ã§ã³ ID ãåããªãã£ãããšããææªã±ãŒã¹ã§ãããã®å Žåãè€æ°ã®ã»ãã·ã§ã³ãå
šéš session_id: "default" ãšããŠèšé²ãããŠããŸããã»ãã·ã§ã³åäœã®éèšãæå³ããªããªããªããŸããå®éã«ãã®ãã¹ã«èœã¡ããç°åžžäºæ
ãªã®ã§ãåŸè¿°ãããããã°ã®éã«ãdefault ãšãã ID ã®è¡ãæºãŸã£ãŠããããšããçŸè±¡ãæãããã«ãªãããšããããŸãã
ã³ã¹ãèšç®ã®ç²ŸåºŠãš Math.round() ã®æå³
128ã133è¡ç®ã®ã³ã¹ãèšç®ã®æžãæ¹ã¯ããç¹åŸŽçã§ãã
const estimatedCostUsd = Math.round((
(inputTokens / 1e6) * rates.in +
(outputTokens / 1e6) * rates.out +
(cacheWriteTokens / 1e6) * rates.cacheWrite +
(cacheReadTokens / 1e6) * rates.cacheRead
) * 1e6) / 1e6;
* 1e6 ããŠãã Math.round() ãããŸã / 1e6 ã§æ»ããŠããŸããããã¯æµ®åå°æ°ç¹ã®äžžã誀差ãå°æ°ç¹ä»¥äž6æ¡ã§æããããã§ãã
JavaScript ã® Number 㯠IEEE 754 å粟床浮åå°æ°ç¹ãªã®ã§ã0.1 + 0.2 === 0.3 ã false ã«ãªãã®ãšåãçç±ã§ãè€æ°ã®ã¬ãŒããæãåããã环ç©ã«äžžã誀差ãä¹ããŸãã0.000003000000000000001 ã®ãããªå€ã JSONL ã«å
¥ã£ãŠã人éã«ã¯èªã¿ã«ãããåŸã§éèšãããšããèª€å·®ãæ°ã«ãªããŸãã
å°æ°ç¹ä»¥äž6æ¡ã«äžžããŠããã®ã¯ãUSD ã§1ãã€ã¯ããã«æªæºã®ç²ŸåºŠã¯äžèŠããšãã倿ã§ããHaiku ã®ã€ã³ãããå䟡㯠0.80 ãã«/çŸäžããŒã¯ã³ãªã®ã§ã1äžããŒã¯ã³åŠçãããšããŠ0.008ãã«ã1000ããŒã¯ã³ãªã0.0008ãã«ã§ãã6æ¡ããã°ååãªã³ã¹ãæèŠãæŽããŸãã
appendFile ãš JSONL 圢åŒã®èšèšå€æ
150è¡ç®ã®æžãåºãã¯ã·ã³ãã«ã§ãã
appendFile(path.join(metricsDir, 'costs.jsonl'), `${JSON.stringify(row)}\n`);
JSONLïŒJSON LinesïŒãéžãã§ããçç±ã¯ã远èšãå®å
šã«ã§ããããã§ããJSON é
åãã¡ã€ã«ã«è¿œèšããããšãããšãæ«å°Ÿã® ] ãæžãæããªããã°ãªããªããããæžã蟌ã¿éäžã§ããã»ã¹ãæ»ãã ããã¡ã€ã«å
šäœãå£ããŸããJSONL ã¯ã1è¡ = 1ãªããžã§ã¯ããã®åœ¢åŒãªã®ã§ã远èšã¯æ«å°Ÿã«1è¡è¶³ãã ãã§ããéäžã§ããã»ã¹ãèœã¡ãŠãæ¢åã®è¡ã¯ç¡äºã§ãã
Stop hook 㯠Claude Code ã®çµäºã·ãŒã±ã³ã¹ã®äžã§èµ°ããããæžã蟌ã¿äžã«åŒ·å¶çµäºããããªã¹ã¯ããŒãã§ã¯ãããŸãããããããç¶æ³ã§ãããŒã¿ãæå°éã®æå€±ã§æžã圢åŒãéžãã§ããŸãã
ensureDir(metricsDir) 㯠~/.claude/metrics/ ãååšããªããã°äœããŠãŒãã£ãªãã£ã§ããStop hook ãåããŠå®è¡ãããšãã«ãã£ã¬ã¯ããªãååšããªããš appendFile ã倱æãããããæ¯åãã§ãã¯ããŠããŸããfs.mkdirSync(path, { recursive: true }) ã®ã©ãããŒã§ããã£ã¬ã¯ããªãååšããå Žåã¯äœãããŸããã
process.stdout.write(raw) ââ ãã€ãã©ã€ã³ãå£ããªãããã®èŠçŽ
æåŸã®1è¡ïŒ156è¡ç®ïŒã®æå³ãååã§è§ŠããŸããããããå°ãæãäžããŸãã
process.stdout.write(raw);
ECCïŒEnhanced Claude CodeïŒã®ããã¯èŠçŽã§ã¯ãStop hook 㯠stdin ã stdout ã«ãã®ãŸãŸæµãããšãæ±ããããŠããŸããããã¯è€æ°ã® Stop hook ãçŽåã«ç¹ãããã§ãã
Claude Code ã® settings.json ã§ã¯ Stop ã»ã¯ã·ã§ã³ã«è€æ°ã®ã³ãã³ãã䞊ã¹ãããŸãããã®å Žåãåã³ãã³ãã stdin ãåãåããstdout ã«æµãããšã§æ¬¡ã®ã³ãã³ãããã®ããŒã¿ãåãåããŸããã³ã¹ã远跡 hook ãéäžã§ããŒã¿ã飲ã¿èŸŒãã§ããŸããšãåŸç¶ã® hookïŒäŸãã°ã»ã«ãç£æ» hookïŒããã€ããŒããåãåããªããªããŸãã
cost-tracker.js ã¯ã³ã¹ãéèšãå¯äœçšãšããŠè¡ãããã€ãã©ã€ã³äžã®ééçãªäžç¶ç¹ãšããŠæ¯ãèããŸãããã®ããããšã©ãŒãèµ·ããŠã catch {} ã§ã¹ãããããŠæåŸã«å¿
ã process.stdout.write(raw) ãŸã§å°éããããã«ãªã£ãŠããŸããã³ã¹ãèšé²ãæ¬ ããããšããããã€ãã©ã€ã³ãéåããããšã®æ¹ã圱é¿ã倧ããããã§ãã
ç§ãè©°ãŸã£ã話
52æ¥éæ°ã¥ããªãã£ãçç±
~/.claude/metrics/costs.jsonl ã®ãŒãèšé²ã«æ°ã¥ããã®ã¯ã鱿¬¡ã¬ããŒããçæããããšããŠéèšã¹ã¯ãªãããèµ°ããããšãã§ãã52æ¥åã® JSONL ãéèšãããšã2340è¡ãã¹ãŠã® estimated_cost_usd ã 0 ã§ããã
æåã¯ãç¡ææ ã§åããŠããããåœç¶ã§ã¯ïŒããšæããŸãããClaude Code ã® MAX å¥çŽã¯ããŒã¯ã³èª²éã§ã¯ãªãæå®é¡ãªã®ã§ãã³ã¹ãã0ãªã®ã¯æ£ãããšãèšããŸããã§ãããinput_tokens ãŸã§å
šè¡ 0 ãªã®ã¯ãããããå®éã«äœååãã¢ã·ã¹ã¿ã³ããå¿çããŠããã®ã«ãããŒã¯ã³æ°ãèšé²ãããŠããªãã¯ãããããŸããã
åé¡ã¯ãã°ãããšã©ãŒãªãåããŠãããããšã§ããhook ã®å®è¡ã¯æ¯ã»ãã·ã§ã³æ£åžžã«å®äºããŠããŸãããcosts.jsonl ã«ã¯ã¡ãããšè¡ã远èšããç¶ããŠããŸãããã¿ã€ã ã¹ã¿ã³ããæ£ããããã ãæ°å€ãã£ãŒã«ããå
šéšãŒãã§ããã
ãåããŠããããã«èŠãããäžèº«ã空æŽãââãããäžçªçºèŠãé ãããé害ãã¿ãŒã³ã§ãããšã©ãŒãã°ãããã°å³åº§ã«æ°ã¥ããŸãããµã€ã¬ã³ããªèª€åäœã¯ãæèçã«ããŒã¿ã確èªãããŸã§ååšããŸããã
v1 ã®äœããŸããã£ãã
æ§ããŒãžã§ã³ã®ã³ãŒãã¯æå
ã«æ®ã£ãŠããŸããããåäœããéç®ãããšæ§é ã¯æããã§ããStop hook ã® stdin ãã€ããŒããåãåããããããçŽæ¥ usage ã model ãåãåºãããšããŠããŸããã
// v1 ã®æ³å®ïŒå®éã¯ååšããªããã£ãŒã«ãïŒ
const { usage, model, session_id } = JSON.parse(raw);
const inputTokens = usage?.input_tokens ?? 0;
Stop hook ã® stdin ã«æ¥ãå®éã®ãã€ããŒã㯠settings.json ã®ã³ã¡ã³ãïŒ9ã10è¡ç®ïŒã«æžããŠããéãã§ãusage ã model ãå«ãŸããŠããŸããã
{ session_id, transcript_path, cwd, hook_event_name, ... }
usage?.input_tokens ã¯ãªãã·ã§ãã«ãã§ãŒã³ãªã®ã§ undefined ãè¿ãã?? 0 ã§ãŒãã«ãªããŸãããšã©ãŒã¯äžåçºçããããã æ¯åãŒããèšé²ãããŸãã
ããããèŠèœãšããŠããåæã¯ãStop hook ã® stdin ã«ã¯ã»ãã·ã§ã³å šäœã®ã³ã¹ãæ å ±ãéçŽãããŠå ¥ã£ãŠããããšããæã蟌ã¿ã§ãããå®éã¯ããã§ã¯ãããŸãããStop hook ã®ãã€ããŒãã¯ã»ãã·ã§ã³ç®¡çã«å¿ èŠãªæå°éã®æ å ±ã ãã§ãå ·äœçãªããŒã¯ã³äœ¿çšéã¯ãã©ã³ã¹ã¯ãªãããã¡ã€ã«ãèªãŸãªããã°åããŸãããClaude Code ã®å ¬åŒããã¥ã¡ã³ãã«ãæç€ºçãªèšè¿°ããªãããã®æåã¯ãã©ã³ã¹ã¯ãªãããå®éã«èªãã§ç¢ºããããŸã§ããããŸããã§ããã
transcript_path ãåããªããšãã®æ²Œ
v2 ã«æžãçŽããåŸãå¥ã®è©°ãŸãããããŸãããããŒã«ã«ç°å¢ã§ã¯æ£åžžã«åããŠããã®ã«ãlaunchd çµç±ã§èµ·åããã»ãã·ã§ã³ã® hook ããŒãã®ãŸãŸèšé²ãããè¡ãæ£çºçã«æ··ããã®ã§ãã
調ã¹ããšãtranscriptPath && fs.existsSync(transcriptPath) ã®æ¡ä»¶ã false ã«ãªã£ãŠããã±ãŒã¹ããããŸãããåå ã¯2ã€ãããŸããã
1ã€ç®ã¯ PATH ã®åé¡ã§ãã launchd ã®ç°å¢å€æ° PATH 㯠/usr/bin:/bin:/usr/sbin:/sbin ãåºæ¬ã§ãNode.js ã®ãã /opt/homebrew/bin ã nvm é
äžãå«ãŸããŸãããcost-tracker.js 㯠Node.js ã¹ã¯ãªãããªã®ã§ãPATH ãæ£ãããªããš hook ãã®ãã®ãèµ·åãããClaude Code ã fallback ãšã㊠ãã€ããŒãããã®ãŸãŸæµãæåã«ãªããŸãããã®å Žå costs.jsonl ã«ã¯äœãæžãããªããããè¡æ°ãå°ãªãæ¥ = hook ãåããŠããªãæ¥ããšããåœ¢ã§æ€åºã§ããŸããplist ã« PATH ãæç€ºçã«èšå®ããããšã§è§£æ¶ããŸããã
2ã€ç®ã¯ transcript_path ã®çµ¶å¯Ÿãã¹åé¡ã§ãã Claude Code ãã»ãã·ã§ã³ JSONL ãã¡ã€ã«ãæžããã¹ã¯ ~/.claude/projects/ 以äžã®åºå®äœçœ®ã§ãããtranscript_path ãã£ãŒã«ãã«å
¥ã£ãŠããæååãç°å¢ã«ãã£ãŠã¯çžå¯Ÿãã¹ã«ãªã£ãŠããã±ãŒã¹ããããŸãããfs.existsSync ã¯çžå¯Ÿãã¹ãã«ã¬ã³ããã£ã¬ã¯ããªåºæºã§è§£æ±ºãããããlaunchd ããèµ·åããå Žåã® CWDïŒéåžž /ïŒã§å±éããããšåœç¶ãã¡ã€ã«ã¯èŠã€ãããŸããã
ä¿®æ£ã¯ transcript_path ãåãåã£ãã path.resolve(transcript_path) ã§çµ¶å¯Ÿåããããšã§ãããã ãçŸåšã® v2 ã³ãŒããèŠããš path.resolve() ã¯å
¥ã£ãŠããŸãããå®éã«åé¡ãåçŸããç°å¢ã§ã®ã¿çžå¯Ÿãã¹ãæ¥ãŠãããéåžžã® Claude Code ã»ãã·ã§ã³ã§ã¯çµ¶å¯Ÿãã¹ãæ¥ãããããªã¹ã¯ã¯éå®çãšå€æããŠå¯Ÿå¿ãä¿çããŠããŸãããããlaunchd çµç±ã§ã®ã¿ãŒãèšé²ãæ£çºããããšããçç¶ãåºããããŸããããçãã®ãè¿éã§ãã
null è¿ãããéæãªãŒããã«ãªãåé¡
sumUsageFromTranscript() ã¯èªã¿åãã«å€±æãããš null ãè¿ããŸãïŒ62è¡ç®ã® return nullïŒãåŒã³åºãåŽã® 125è¡ç®ã§ããå±éãããŸãã
const {
inputTokens = 0,
outputTokens = 0,
// ...
} = usageTotals || {};
null ãæ¥ããš {} ã«å±éããããã¹ãŠã®ããã©ã«ãå€ 0 ã䜿ãããŸããçµæãšããŠããtranscript ãèªããªãã£ãè¡ããšãtranscript ãèªãã ãæ¬åœã«ããŒã¯ã³ããŒãã ã£ãè¡ïŒã»ãŒããããªãïŒãããcosts.jsonl äžã§ã¯åºå¥ã§ããŸããã
ããã倿ããã®ã¯ããããæ¥ã®èšé²ãåŠã«å°ãªãããšãããšãã«æåã§ costs.jsonl ãéããŠç¢ºèªãããšãã§ããtranscript_path: "" ãšããè¡ãæ··ãã£ãŠããŸãããtranscript_path ã空æåã®ãšããfs.existsSync("") 㯠false ãè¿ããã sumUsageFromTranscript() ãåŒã°ãããusageTotals 㯠null ã®ãŸãŸãçµæãŒãèšé²ã«ãªããŸãã
æ ¹æ¬åå 㯠session_id ãåããŠããŠã transcript_path ãåããªãã±ãŒã¹ããããšããããšã§ããv2 ã³ãŒãã® 141è¡ç®ã« transcript_path: transcriptPath || '' ãšããã®ã§ããã¹ãåããªããšãã¯ç©ºæåãèšé²ãããŸããåŸããéèšã¹ã¯ãªããã§ transcript_path ã空ã®è¡ãé€å€ããããestimated_cost_usd === 0 && input_tokens === 0 && transcript_path === "" ã®æ¡ä»¶ã§ãŒãèšé²ããããŒã¿ãªãããšããŠæ±ãããšã§å¯ŸåŠããŠããŸãã
ã环ç©èšèšããç¥ããã«ã³ã¹ãã7åã«èšç®ãã話
v2 ãæ£ããåãå§ããåŸãæ¥æ¬¡ã³ã¹ãéèšã¹ã¯ãªãããæžãããšãã«ãŸãè©°ãŸããŸããã1æ¥ã®ã³ã¹ããåºãããšããŠå šè¡ãåçŽåèšãããšãå®éã®æèŠããäœåãå€ãæ°åãåºãŸããã
ã³ã¡ã³ãã® 19ã21 è¡ç®ïŒååã§åŒçšïŒãèªã¿çŽããŠããã环ç©èšèšã ããšæ°ã¥ããŸãããStop hook ã¯ã¢ã·ã¹ã¿ã³ãã¿ãŒã³ããšã«çºç«ããããã1ã»ãã·ã§ã³ã§ 10 åå¿çãããã° 10 è¡ã远èšããããããããã1åç®ãŸã§ãã2åç®ãŸã§ããšãã环ç©ããŒã¯ã³æ°ãæã£ãŠããŸããå šè¡ãè¶³ããšã1ã¿ãŒã³ç®ã®ããŒã¯ã³ã10ååã2ã¿ãŒã³ç®ã®ããŒã¯ã³ã9ååâŠãšäºéäžéã«èšäžãããŸãã
æ£ããéèšã®æžãæ¹ã¯ãåã session_id ã®è¡ãã°ã«ãŒãåããŠãåã°ã«ãŒãã®æåŸã®1è¡ã ããåãããšã§ãã
const sessions = {};
for (const line of lines) {
const row = JSON.parse(line);
sessions[row.session_id] = row; // åŸããæ¥ãè¡ã§äžæžãããã
}
const dailyCost = Object.values(sessions)
.filter(r => r.timestamp.startsWith('2026-07-08'))
.reduce((sum, r) => sum + r.estimated_cost_usd, 0);
ãã®ãã°ãå ¥ããç¶æ ã§ã1æ¥ã®ã³ã¹ãããèšç®ãããšãã»ãã·ã§ã³å ã®å¿çåæ°ãå€ãã»ã©æ°åãèšãã¿ãŸãã10åå¿çããã»ãã·ã§ã³ã¯1åå¿çããã»ãã·ã§ã³ã®çŽ10åã®ãèŠããã®ã³ã¹ããã«ãªããŸãããå®éã«ã¯åãã¢ãã«ã䜿ã£ãŠããã¯ããªã®ã«æ°åã«å€§ããªã°ãã€ããåºãåœ¢ã§æ°ã¥ããŸããã
ãããŒã¿ã¯æ£ããèšé²ãããŠãããã¬ããŒããåãã€ããŠãããââ ãããããšã©ãŒãªãåããŠããããã«èŠããæ²Œã®ã²ãšã€ã§ããéèšããžãã¯ãæžããšãã¯ããµã³ãã«ããŒã¿ãæåã§ç¢ºèªããŠããå®è£
ããã®ã確å®ã§ãããããã®å Žåãsession_id ãåãè¡ã grep ã§æãåºããŠç®èŠãããŸã§ãèšèšã®æå³ã«æ°ã¥ããŸããã§ããã
ãããã°ã«äœ¿ããã¯ã³ã©ã€ããŒ
è©°ãŸã£ããšãã«æå ã§ãã䜿ã£ãã³ãã³ããæ®ããŠãããŸãã
ãŒãèšé²ã®è¡æ°ã確èªãã:
node -e "
const fs = require('fs');
const lines = fs.readFileSync(process.env.HOME + '/.claude/metrics/costs.jsonl','utf8')
.split('\n').filter(Boolean).map(JSON.parse);
const zeros = lines.filter(r => r.input_tokens === 0);
console.log('total:', lines.length, 'zeros:', zeros.length);
"
transcript_pathã空ã®è¡ãæ¢ã:
node -e "
const fs = require('fs');
const lines = fs.readFileSync(process.env.HOME + '/.claude/metrics/costs.jsonl','utf8')
.split('\n').filter(Boolean).map(JSON.parse);
lines.filter(r => !r.transcript_path).forEach(r => console.log(r.timestamp, r.session_id));
"
v2 ã«åãæ¿ããåŸã«äžèšãå®è¡ãããšãã1è¡ç®ã®ã³ãã³ãã§ zeros: 0 ãåºãã°ãã¹ãŠã®èšé²ã«ããŒã¯ã³æ°ãå
¥ã£ãŠããŸãããŒããæ®ã£ãŠããå Žåã¯ãtranscript_path ã®ååŸã«åé¡ããããããã©ãŒã«ããã¯ãã¹ã«èœã¡ãŠããã±ãŒã¹ã§ãã
52æ¥åã®ãŒãèšé²ãæšãŠããããªãã£ãã®ã¯çãã£ãã§ãããv2 ã«åãæ¿ããŠããã¯æ¯ã»ãã·ã§ã³æ£ç¢ºãªããŒã¯ã³æ°ãç©ãŸããŠããŸããèªåŸç°å¢ã®ã³ã¹ãæèŠãæãŠãããã«ãªã£ãŠåããŠããã©ã®äœæ¥ãæ¶è²»ãå€ããã©ããå¹çåãã¹ããããšããè°è«ãã§ããããã«ãªããŸããããŒãã䞊ã¶ãã°ã¯ãåããŠãã蚌æ ãã§ã¯ãªããäœãèšé²ããŠããªã蚌æ ãã§ãââãã®ããšãã2340è¡ãããŠåŠã³ãŸããã
ã€ãŸãããã€ã³ã
åæ®µã§ç޹ä»ããã52æ¥ãŒãèšé²ããcumulativeèšèšã®èª€éèšããlaunchd PATHæ¬ èœãã®ã»ãã«ããStop hookãå®éçšããŠãããšè©°ãŸãå Žæã¯ããã€ããããŸããããã§ã¯ç¶²çŸ çã«åæããŸãã
â ã¢ãã«åãå°æ¥ãããããªããªã
getRates() ã¯æååã« 'haiku' 'opus' ãå«ãŸãããã©ããã§æ¯ãåãããã以å€ã¯ Sonnet ãšããŠæ±ããŸããçŸè¡ã®ã¢ãã«åœåèŠåïŒclaude-haiku-4-5-20251001 ãªã©ïŒã§ã¯ããã§ååã§ãããå°æ¥ Anthropic ããã¡ããªãŒåã倿ŽãããšãïŒããšãã° haiku ç³»åã«å¥ç§°ãã€ãããªã©ïŒãããããå€ããŠãã¹ãŠã Sonnet ã¬ãŒãã«ãªããŸãããšã©ãŒã¯åºããã¬ãŒãã ãéãã«ãããŸãã宿çã«ã³ãŒããšææ°ã¢ãã«åãç
§åããéçšããããã倱ææã«ãã°ãæ®ãåå²ãå
¥ããŠããã®ãå®å
šã§ãã
â¡ Stop hookãçºç«ããªãã±ãŒã¹
Claude Code ãããã»ã¹ã SIGKILL ã§åŒ·å¶çµäºãããå ŽåïŒã·ã¹ãã OOMãããããªãŒåããªã©ïŒãStop hookã¯å®è¡ãããŸãããæ£åžžãª /exit ãšã¿ã€ã ã¢ãŠãã§ã¯çºç«ããŸããã匷å¶çµäºã¯ã»ãã·ã§ã³çµäºã·ãŒã±ã³ã¹ãã¹ãããããããããã®ã»ãã·ã§ã³åã®èšé²ãäžžããšæ¬ èœããŸããæéã³ã¹ãéèšã®èª€å·®ãæ°ã«ãããªããèšé²ããªã = ãŒãã§ã¯ãªãããŒã¿æ¬ æããšããè§£éãéèšã¹ã¯ãªããåŽã«å
¥ããŠããå¿
èŠããããŸããå
·äœçã«ã¯ãã»ãã·ã§ã³æ°ãšè¡æ°ã®æ¯çãç£èŠããŠãæåŸ
è¡æ°ããèããå°ãªãæ¥ããã¢ã©ãŒãã®å¯Ÿè±¡ã«ããæ¹æ³ããããŸãã
⢠é·å€§ãã©ã³ã¹ã¯ãªããã®åæèªã¿èŸŒã¿ããããã³ã°
sumUsageFromTranscript() 㯠fs.readFileSync() ã䜿ã£ãŠããŸããClaude Code ã®é·ãã»ãã·ã§ã³ïŒæ°åã¿ãŒã³ä»¥äžã®è€éãªå®è£
ã¿ã¹ã¯ïŒã§ã¯ããã©ã³ã¹ã¯ãªãã JSONL ãæ°åMB ã«ãªãããšããããŸããreadFileSync ã¯åæåŒã³åºããªã®ã§ããã®é Node.js ã®ã€ãã³ãã«ãŒããæ¢ãŸããŸããStop hook èªäœã¯åçºå®è¡ãªã®ã§ã€ãã³ãã«ãŒããæ¢ãŸã£ãŠãåé¡ã¯ãããŸããããèªã¿èŸŒã¿ã«2ã3ç§ããããš Claude Code ã®ã»ãã·ã§ã³çµäºã¬ã¹ãã³ã¹ãé
ãã圢ãšããŠäœæãããŸãã宿ž¬ã§åé¡ã«ãªã£ãå Žåã¯ãã¹ããªãŒã ã§å
é ããè¡åäœã«èªãå®è£
ã«åãæ¿ããããšã§ããã¡ã€ã«å
šäœãäžåºŠã«ã¡ã¢ãªã«èŒããã®ãé¿ããããŸãã
⣠hook chain ã§ã®stdinç«¶å
Stop ã»ã¯ã·ã§ã³ã«è€æ°ã® hook ãç»é²ããå Žåãå hook ã¯çŽåã«å®è¡ãããåã® hook ã® stdout ãæ¬¡ã® hook ã® stdin ã«ãªããŸããcost-tracker.js ã®æåŸã® process.stdout.write(raw) ã¯ããã®èŠçŽãå®ãããã®ãã®ã§ãããã chain ã®éäžã« process.exit(0) ãåŒã¶ hook ãå²ã蟌ãããstdout ã« raw 以å€ã®ããŒã¿ãæµã hook ãååšãããšãåŸç¶ã® hook ããã€ããŒããæ£ããåãåããªããªããŸããchain ã®å hook ãæžããšãã¯ãstdin ãåãåããstdout ã«ãã®ãŸãŸæµãã圢ã培åºããå¿
èŠããããŸããcost-tracker ã«éãããStop hook å
šè¬ã«èšããèŠåŸã§ãã
†costs.jsonl ã®è¥å€§å
Stop hook ã¯ã¢ã·ã¹ã¿ã³ãã¿ãŒã³ããšã«çºç«ãã1è¡è¿œèšããŸãã1æ¥10ã»ãã·ã§ã³ã§å¹³å20ã¿ãŒã³ãªãã1æ¥200è¡ãç©ãŸããŸãã1幎ç¶ç¶ãããš7äžè¡ãè¶
ããŸããcosts.jsonl èªäœã¯åè¡ãæ°çŸãã€ãçšåºŠãªã®ã§ã¹ãã¬ãŒãžã¯ãã»ã©åé¡ã«ãªããŸããããåŸããæžãéèšã¹ã¯ãªãããå
šè¡ãèªãæ§é ã ãšãæéãçµã€ã»ã©ã¹ãã£ã³æéãå¢ããŸãã宿çã«å€ãè¡ã幎次ãã¡ã€ã«ã«ã¢ãŒã«ã€ãããéçšããéèšã¯ãšãªããçŽè¿Næ¥ã ãã¹ãã£ã³ããã圢ã«ããŠãããšé·æéçšã楜ã§ãã
⥠ã¿ã€ã ãŸãŒã³åé¡
new Date().toISOString() ãæžã timestamp 㯠UTC ã§ããæ¥æ¬¡éèšã JST åºæºã§ããããå Žåã2026-07-09T00:30:00.000Z ãšããè¡ã¯ JST ã§ã¯7æ9æ¥ã§ãããUTC ã§ã¯7æ8æ¥ã®ã«ãŠã³ãã«ãªããŸããæ¥ããŸããæ·±å€ã®ã»ãã·ã§ã³ã§éèšæ¥ä»ããããçŸè±¡ãšããŠçŸããŸããä¿®æ£ã¯ timestamp ãåãåºããšãã« +9h ã㊠JST ã®æ¥ä»æååã«å€æããããšã§ãããJSONL ã«å
¥ã£ãŠããå€ã¯ UTC ã®ãŸãŸã«ããŠãããéèšæã«å€æããæ¹ãäžè²«æ§ãä¿ãŠãŸããJSONL ã®å€ã JST ã§æžãçŽããšãåŸãã UTC ããŒã¹ã«æ»ããããªã£ããšãã«å
ããŒã¿ã倱ãããŸãã
⊠æžã蟌ã¿äžãã©ã³ã¹ã¯ãªããã®èªã¿èŸŒã¿ã¿ã€ãã³ã°
Claude Code ãã¢ã·ã¹ã¿ã³ãã¿ãŒã³ãæžããŠããæäžã« Stop hook ãèµ°ããšããžã±ãŒã¹ããããŸãïŒéåžžã«çãã¿ãŒã³ã®å¿çåŸãªã©ïŒããã®ãšãããã©ã³ã¹ã¯ãªãã JSONL ã®æ«å°Ÿè¡ãäžå®å
šãª JSON ã§çµãã£ãŠããå¯èœæ§ããããŸããsumUsageFromTranscript() ã®ããŒã¹éšå㯠try { entry = JSON.parse(line); } catch { continue; } ã§è¡åäœã«åŠçããã®ã§ãå£ããæ«å°Ÿè¡ã¯ continue ã§ã¹ããããããŸããçµæãšããŠããã®è¡åã®ããŒã¯ã³ãéèšãããªãïŒçŽåã®æ£åžžè¡ãŸã§éèšãããïŒãšããæåã«ãªããŸããå®éã«ãããåé¡ã«ãªãããšã¯ã»ãŒãããŸãããããã³ã¹ãã埮åŠã«å°ãªãèšé²ããããããšã®äžå ã«ã¯ãªãããŸãã
â§ session_idéè€ãš 'default' ãã©ãŒã«ããã¯
åæ®µã§è§Šããéããsession_id ãã©ããããåããªãå Žå㯠'default' ãšããåºå®å€ã«ãªããŸããããè€æ°ã® Claude Code ã€ã³ã¹ã¿ã³ã¹ãåæã«åããŠããŠïŒlaunchd ãžã§ãã䞊走ããã±ãŒã¹ãªã©ïŒãããããã 'default' ãšããŠèšé²ããããšãcumulative èšèšã®ãsession_id ã§ã°ã«ãŒãåããŠæåŸã®1è¡ã䜿ããéèšã厩ããŸãã'default' ã§ã°ã«ãŒããããè¡çŸ€ã®ãã¡æåŸã®1è¡ã ãã䜿ããããããæãå°ããã»ãã·ã§ã³åã ã£ããæã倧ããåã ã£ãããšãçµæãäžå®ã«ãªããŸããsession_id: "default" ã®è¡ã宿çã« grep ã§ç¢ºèªããŠãçºçé »åºŠãææ¡ããŠããããšãããããããŸãã
âš require() ãš Node.js ããŒãžã§ã³ã®äžäžèŽ
cost-tracker.js 㯠CommonJSïŒ'use strict'; const fs = require('fs');ïŒã§æžãããŠããŸããnvm ã§ Node.js ã®ããŒãžã§ã³ãäžããçŽåŸããäŸåã©ã€ãã©ãªã ESM å°çšã«ç§»è¡ããå Žåãrequire() ã§ ESM ã¢ãžã¥ãŒã«ãããŒãããããšã㊠ERR_REQUIRE_ESM ãšã©ãŒãåºãŸãããã®ãšã©ãŒã¯ hook å®è¡æã« Node.js ã stderr ã«åããŸãããClaude Code ã®éåžžåºåã«ã¯çŸããŸãããhook ãéãã«å€±æã㊠costs.jsonl ã«äœãæžãããªã圢ã«ãªããããããŒãèšé²ããšåºå¥ãã€ããŸãããnode --version ãšäŸåé¢ä¿ã® ESM 察å¿ç¶æ³ã Node.js ã¢ããã°ã¬ãŒãæã«å¿
ã確èªããå¿
èŠããããŸãã
ãã¹ããã©ã¯ãã£ã¹
52æ¥éã®å€±æãšãããããã® v2 ãžã®æžãçŽãããã®åŸã®çްããè©°ãŸããçµãŠãStop hook ã§ã³ã¹ã远跡ãããéã®ãã¹ããã©ã¯ãã£ã¹ãšããŠãŸãšãããããã®ã¯æ¬¡ã®éãã§ãã
â Stopããã¯ã§ä¿¡é Œã§ããããŒã¿ãœãŒã¹ã¯transcriptãã¡ã€ã«ã ã
æãéèŠãªæèšã§ããstdin ãã€ããŒãã«ã¯ããŒã¯ã³äœ¿çšéã¯å
¥ã£ãŠããŸãããtranscript_path ãåãåããã»ãã·ã§ã³ JSONL ãèªåã§èªãããã®èšèšã«æ°ã¥ããŸã§ã®52æ¥ãããã®èšäºå
šäœã®èæ¯ã«ããäºå®ã§ããäœããèšæž¬ããããšãããªãããŸããã®ããŒã¿ãã©ãã«ãããã確èªããããããã¯ãã ããšããåæã§ã³ãŒããæžããšããµã€ã¬ã³ãã«åãã€ãããŒã«ãã§ãããããŸãã
â¡ Stop hookã¯çµ¶å¯Ÿã«å€±æãããªã
ã³ã¹ãèšé²ãæ¬ ããããšããããStop hook ããšã©ãŒãåã㊠Claude Code ã®ã»ãã·ã§ã³çµäºã·ãŒã±ã³ã¹ã«åœ±é¿ããæ¹ãåé¡ã§ããã¡ã€ã³åŠçå
šäœã try { ... } catch { } ã§å
ã¿ããšã©ãŒãèµ·ããŠã process.stdout.write(raw) ãŸã§å¿
ãå°éããèšèšã«ãããcost-tracker.js ã®151ã156è¡ããã®ãã¿ãŒã³ã§ããããã°ãæ¬ ããæ¥ããã£ãŠããããã»ãã·ã§ã³ãå£ããŠã¯ãããªãããšããåªå
é äœã®å€æã§ãã
⢠stdin â stdout ã®ééãã¿ãŒã³ãå®ã
chain å
ã®å
š hook ã stdin ãåã㊠stdout ã«æµã ãšããèŠçŽãå®ãã°ãä»»æã®æ°ã® hook ãçŽåã«ç¹ããŸãããã®èŠçŽãç ŽããšãåŸç¶ã® hook ãéãã«åããªããªããŸããhook ãæžããšãã¯æ©èœå®è£
ãå¯äœçšã«çãããã€ãã©ã€ã³ãžã®åœ±é¿ãæå°åããçºæ³ã倧åã§ãã
⣠åå€æã¯ toNumber() ã§é²åŸ¡çã«äžå åãã
å€éšããŒã¿ïŒãã©ã³ã¹ã¯ãªãã JSONLïŒããèªãã å€ãçŽæ¥ç®è¡æŒç®ã«äœ¿ããªããNaN ãäžç®æã§ã环ç©åèšã«æ··å
¥ãããšããã®éèšå
šäœã NaN ã«ãªããŸããNumber.isFinite() ã䜿ã£ãŠ NaN ãš Infinity ãäž¡æ¹åŒŸã颿°ãäžå眮ããŠããã3è¡ã§æžããŠãæ°é±éåŸã®èªåãå©ããŸãã
†远èšãã¡ã€ã«ã¯JSONLã§æžã
JSON é åã§ã¯ãªã JSON LinesïŒ1è¡1ãªããžã§ã¯ãïŒãéžã¶çç±ã¯ã·ã³ãã«ã§ããJSON é åãžã®è¿œèšã¯ãã¡ã€ã«æ«å°Ÿãæžãæãããããéäžã§ããã»ã¹ãèœã¡ããšãã¡ã€ã«å šäœãå£ããŸããJSONL ãªã远èšã¯æ«å°Ÿã«1è¡è¶³ãã ããStop hook 㯠Claude Code ã®çµäºã·ãŒã±ã³ã¹äžã§åããããæžã蟌ã¿äžã«åŒ·å¶çµäºããããªã¹ã¯ããŒãã§ã¯ãããŸãããJSONL ã¯ãã®ç¶æ³ã§ãæå°éã®æå€±ã§æžã圢åŒã§ãã
⥠éèšã¯session_idæçµè¡ã䜿ã
Stop hook ã¯ã¿ãŒã³ããšã«çºç«ããããã1ã»ãã·ã§ã³ãè€æ°è¡ã«ãªããŸããå
šè¡ãåçŽåèšãããšã³ã¹ããäœåã«ãèšãã¿ãŸããéèšã¯ session_id ã§ã°ã«ãŒãåããåã°ã«ãŒãã®æåŸã®1è¡ã ãã䜿ãããã®èšèšã¯ã³ã¡ã³ãïŒcost-tracker.js ã®19ã21è¡ç®ïŒã«æèšãããŠããŸãããã³ãŒããèªãŸãã«éèšã¹ã¯ãªãããæžããšå¿
ãèžã穎ã§ãã
const sessions = {};
for (const line of lines) {
const row = JSON.parse(line);
sessions[row.session_id] = row; // åŸã®è¡ã§äžæžã â èªåçã«æçµè¡ãæ®ã
}
⊠launchd plistã«PATHãæç€ºãã
launchd ã®å®è¡ç°å¢ã¯ /usr/bin:/bin:/usr/sbin:/sbin ãåºæ¬ PATH ã§ãNode.js ã /opt/homebrew/bin ã«ããå Žåã nvm é
äžã«ããå ŽåããPATH ãéã£ãŠããªããã° hook ãèµ·åããŸãããéãã«å€±æã㊠costs.jsonl ã«äœãæžãããªã圢ã«ãªããŸããplist ã« <key>EnvironmentVariables</key> ã§ PATH ãæç€ºããNode.js ã®å®ãã¹ãå«ããŠããããã®å¯ŸåŠã¯ launchd ã§åãå
šã¹ã¯ãªããã«å
±éã®åºæ¬ã§ãã
â§ metricsãã£ã¬ã¯ããªãæ¯å ensureDir ã§äœã
~/.claude/metrics/ ãååšããªãç¶æ
ã§ appendFile ãåŒã¶ãšãšã©ãŒã«ãªããŸããhook ãåããŠã»ããã¢ããããçŽåŸãããã£ã¬ã¯ããªã誀ã£ãŠæ¶ããå Žåã«çºçããŸããfs.mkdirSync(path, { recursive: true }) ã®ã©ãããŒãæ¯ååŒã¶ããã£ã¬ã¯ããªãååšããã°äœãããªãã®ã§ããªãŒããŒãããã¯ã»ãŒãŒãã§ããããã£ãŠåœç¶ãã®ãã£ã¬ã¯ããªãåæã«ããã³ãŒãã¯ãç°å¢ãå€ãããšéãã«å£ããŸãã
âš stdin ã«äžéãèšãã
const MAX_STDIN = 64 * 1024;
if (raw.length < MAX_STDIN) raw += chunk.substring(0, MAX_STDIN - raw.length);
Stop hook ã®ãã€ããŒãèªäœã¯æ°çŸãã€ãã§ãããäžéãªãã«èªããšäœããã®ç°åžžã§å·šå€§ãªå ¥åãæµã蟌ãã ãšã Node.js ã®ããŒããå§è¿«ããŸãã64KB ãäžéã«ããããšã§ãéåžžã±ãŒã¹ã«ã¯åœ±é¿ãªããç°åžžã±ãŒã¹ã§è©°ãŸããªãèšèšã«ãªããŸãã
â© session_id ã¯ãµãã¿ã€ãºããŠãã䜿ã
ã»ãã·ã§ã³ ID ããã®ãŸãŸãã¡ã€ã«ã«æžããšãå°æ¥ãã®ãã£ãŒã«ãã§ãã¡ã€ã«åãäœã£ãããã¹ã«çµã¿èŸŒãã ããããšãã«ã€ã³ãžã§ã¯ã·ã§ã³ãªã¹ã¯ãçããŸããè±æ°åã»ãã€ãã³ã»ã¢ã³ããŒã¹ã³ã¢ä»¥å€ãé€å»ãããµãã¿ã€ãºãäžæããŸããŠãããsanitizeSessionId() ã session-bridge.js ã«åãåºããŠããã®ã¯ãåãããžãã¯ãè€æ° hook ã§äœ¿ãããããã§ããå
±éããžãã¯ã¯ lib/ ã«çœ®ã㊠import ãããææãã§éè€ã³ããŒããªãããšããåŸã®ä¿å®ã³ã¹ããäžããŸãã
⪠çžå¯Ÿãã¹ã¯path.resolve()ã§çµ¶å¯Ÿåãã
fs.existsSync(transcriptPath) ã¯çžå¯Ÿãã¹ãã«ã¬ã³ããã£ã¬ã¯ããªåºæºã§è§£æ±ºããŸããlaunchd ããèµ·åããå Žåã® CWD ã¯éåžž / ãªã®ã§ãçžå¯Ÿãã¹ãæ¥ããšæå³ããªãå Žæãåç
§ããŸããtranscript_path ãåãåã£ãçŽåŸã« path.resolve(transcriptPath) ã§çµ¶å¯Ÿåããäžè¡ãå
¥ããŠãããšãèµ·åå
ã«äŸåããªãåäœã«ãªããŸããçŸè¡ã® v2 ã§ã¯ãã®åŠçãçãããŠãããéåžžã® Claude Code ã»ãã·ã§ã³ã§ã¯åé¡ãªãã§ãããlaunchd çµç±ã§ã®æ£çºçãªãŒãèšé²ãæ°ã«ãªãå Žåã¯æåã«çãç®æã§ãã
â« ã¿ã€ã ãŸãŒã³ã¯UTCã§èšé²ã»å€æã¯éèšæã«è¡ã
JSONL ã® timestamp 㯠new Date().toISOString()ïŒUTCïŒã§èšé²ããJST ãžã®å€æã¯éèšã¹ã¯ãªããåŽã§ãããå
ããŒã¿ãç¹å®ã¿ã€ã ãŸãŒã³ã§æžãçŽããšãå
ã«æ»ããªããç¶æ³ãçãŸããŸããUTC ã§ä¿åããŠããã°åŸããä»»æã®ã¿ã€ã ãŸãŒã³ã«å€æã§ããŸããæ¥æ¬¡éèšã JST åºæºã§ãããªã new Date(timestamp).toLocaleDateString('ja-JP', { timeZone: 'Asia/Tokyo' }) ã§å€æããéèšé¢æ°ãäžæ¬æžãã°æžã¿ãŸãã
⬠ããŒãèšé²ããšãããŒã¿ãªãããåºå¥ãã
transcript_path ã空æåã®è¡ãšãtranscript ãèªããŠæ¬åœã«ããŒã¯ã³ãŒãã ã£ãè¡ã¯ãçŸè¡ã® JSONL ã§ã¯åºå¥ã§ããŸãããéèšã¹ã¯ãªããã§ãestimated_cost_usd === 0 && input_tokens === 0 && transcript_path === ''ãã®çµã¿åããããããŒã¿ãªãããšããŠé€å€ãããšããŒããæ··ãã£ãæ¥ã®éèšç²ŸåºŠãäžãããŸãããµã€ã¬ã³ããªãŒããä¿¡é Œããããããããã¯ããŒã¿ããªãè¡ã ããšæç€ºçã«ã©ãã«ã§ããæ§é ã«ããæ¹ããé·æéçšã§æ£ç¢ºãªæèŠãä¿ãŠãŸãã
ãŸãšã
Stop hook ã« usage ã model ãæ¥ããšæã£ãŠæžããã³ãŒããã52æ¥éã»2340è¡ã«ããã£ãŠå
šè¡ãŒããåãç¶ããââããã¯ãåããŠããããã«èŠããããšããç¶æ
ã®æããäœæããçµéšã§ããããšã©ãŒãåºãŠããã°ç¿æ¥ã«ã¯æ°ã¥ããã¯ãã§ãããµã€ã¬ã³ãã«åãã€ããã°ã¯ãæèçã«ããŒã¿ã確èªãããŸã§ååšãèŠããŸããã
v2 ãžã®ä¿®æ£ã®æ žå¿ã¯äžè¡ã§ãããstdin ãã usage ãèªããã®ããããŠããstdin ãã transcript_path ãèªã¿ããã®ãã¡ã€ã«ãèªåã§èªããã«å€ãããããã ãã§52æ¥åã®ç©ºæŽãåãŸããŸããã
Stopããã¯ã§ä¿¡é Œã§ããå¯äžã®ããŒã¿ãœãŒã¹ã¯transcriptãã¡ã€ã«ââãã®æèšã¯ Claude Code ã«éããããããã¯ãã€ãã³ãã®ãã€ããŒãã«æ¬²ããããŒã¿ãå ¥ã£ãŠããã¯ãã ããšããåæã§èªååãæžããã¹ãŠã®å Žé¢ã«éããŸãããã€ããŒãã®ä»æ§ã確èªããã«æžãå§ãããšãèªåã§ããŒã¿ãèªã¿ã«è¡ããªããã°ãããªãå Žåã«éãã«å£ããã³ãŒããã§ãããããŸãã
èªåŸç°å¢ã®ç²ŸåºŠã¯ãèšæž¬ã®ç²ŸåºŠã«äŸåããŸããã³ã¹ããæ£ããèšé²ããããŸã§ããã©ã®ã»ãã·ã§ã³ãéããã©ããåãããããšããè°è«ã¯æç«ããŸãããã³ã¹ã远跡ã¯ç°å¢æ§ç¯ã®æåŸã§ã¯ãªããæåã«ç¢ºãããã¹ãåºç€ã§ãã
ä»çµã¿ã®å šäœåã»æ120äžã®å èš³ã»30æ¥æé ã¯æænoteã«ãŸãšããŠããŸãã ð Claude CodeèªåŸç°å¢ã§ãå®éã©ã皌ãã â ä»çµã¿ã»å®äŸã»å§ãæ¹ã»ãµããŒã
LilyïŒ@bokuwalilyïŒâ å人éçºè ãClaude Code ã§èªåååºç€ãçµã¿ãªãããiOSã¢ããªãWebãµãŒãã¹ãéç£ããŠããŸã
- AIã§ãå¯ãŠãŠãåãä»çµã¿ããäœã£ãŠæ120äžã«ãã話㯠noteã®ææèšäº ã«ð°
- OSS: github.com/bokuwalily ð
- ææ°æ å ±ã»ãåãåãã㯠X @bokuwalily ãžð
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ