Skip to content

0-byte lock file permanently blocks usage display (silent failure) #220

@PeterZerg

Description

@PeterZerg

Bug

A 0-byte .usage-cache.lock file permanently prevents usage data from being fetched or displayed. The HUD silently drops the usage section with no error or warning.

Root Cause

Race condition in usage-api.ts lock acquisition. The lock file is created in two steps:

  1. fs.openSync(lockPath, 'wx') — creates file (0 bytes)
  2. fs.writeFileSync(fd, String(Date.now())) — writes timestamp

If the HUD process is killed between steps 1 and 2 (signal, OOM, terminal close, WSL2 lifecycle), a 0-byte lock file remains.

The stale-lock detector in tryAcquireCacheLock cannot recover from this:

// readLockTimestamp (line ~151)
const raw = fs.readFileSync(lockPath, 'utf8').trim();  // "" for 0-byte file
const parsed = Number.parseInt(raw, 10);                // NaN
return Number.isFinite(parsed) ? parsed : null;         // returns null

// tryAcquireCacheLock (line ~186)
const lockTimestamp = readLockTimestamp(lockPath);
if (lockTimestamp != null && Date.now() - lockTimestamp > CACHE_LOCK_STALE_MS) {
//  ^^^ null != null is FALSE — stale check is skipped entirely
    fs.unlinkSync(lockPath);
}
return 'busy';  // stuck forever

Since readLockTimestamp returns null for a corrupt/empty file, the lockTimestamp != null guard prevents the stale check from ever running. Every subsequent render sees "lock is busy" → waits 2s → gives up → no usage data.

Impact

  • Usage percentages (5h, 7d) silently disappear from the HUD
  • Plan label (Max, Pro, etc.) may also disappear from the model bracket
  • No error message shown — user has no indication anything is wrong
  • Persists across sessions until manually deleted

Environment

  • Claude Code v2.1.42
  • claude-hud v0.0.9
  • Ubuntu 24.04 on WSL2
  • Runtime: Bun 1.3.10

Suggested Fix

Treat a null timestamp (0-byte or corrupt lock file) as stale:

- if (lockTimestamp != null && Date.now() - lockTimestamp > CACHE_LOCK_STALE_MS) {
+ if (lockTimestamp == null || Date.now() - lockTimestamp > CACHE_LOCK_STALE_MS) {

This way, a lock file with no valid timestamp is always cleaned up, matching the intent of the stale-lock mechanism.

Workaround

rm ~/.claude/plugins/claude-hud/.usage-cache.lock
rm ~/.claude/plugins/claude-hud/.usage-cache.json

Steps to Reproduce

  1. Start a Claude Code session with claude-hud showing usage
  2. Simulate a kill during lock acquisition: touch ~/.claude/plugins/claude-hud/.usage-cache.lock
  3. Usage display disappears and never recovers

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions