-
Notifications
You must be signed in to change notification settings - Fork 454
Description
Summary
A 0-byte .usage-cache.lock file causes tryAcquireCacheLock() to return 'busy' permanently, preventing usage data from ever refreshing. The HUD falls back to stale cache data indefinitely.
Steps to Reproduce
- Run claude-hud normally (usage displays correctly)
- Simulate a crashed process that creates a 0-byte lock file:
touch ~/.claude/plugins/claude-hud/.usage-cache.lock - Wait for the cache TTL to expire (60s)
- Observe that usage data never refreshes — the HUD returns stale cache forever
Expected Behavior
The lock file should be treated as stale and removed, allowing a fresh API fetch. A lock file with no parseable timestamp is unrecoverable and should not block other processes.
Actual Behavior
readLockTimestamp() returns null for a 0-byte file. The stale-lock check on line 187 of usage-api.ts requires lockTimestamp != null before checking age, so the null case is never cleaned up:
// usage-api.ts:187
if (lockTimestamp != null && Date.now() - lockTimestamp > CACHE_LOCK_STALE_MS) {Since null != null is false, the function falls through to return 'busy' on line 196. The lock persists until manually deleted.
How the 0-byte file is created
If the process crashes between fs.openSync(lockPath, 'wx') (line 171) and fs.writeFileSync(fd, String(Date.now())) (line 173), the lock file is created but never written to. The finally block closes the fd but doesn't clean up on failure.
Suggested Fix
Treat null timestamp as stale (unparseable = unrecoverable):
// Before (buggy):
if (lockTimestamp != null && Date.now() - lockTimestamp > CACHE_LOCK_STALE_MS) {
// After (fixed):
if (lockTimestamp === null || Date.now() - lockTimestamp > CACHE_LOCK_STALE_MS) {This makes any unparseable lock file (0 bytes, corrupt data, NaN) get cleaned up immediately, matching the intent of the stale-lock mechanism.
Environment
- OS: macOS 15.5 (Darwin 25.3.0)
- Bun version: 1.3.10
- Claude Code version: 2.1.74
- claude-hud version: 0.0.9