Skip to content

Explain a stale latest pointer instead of leaking a raw ENOENT - #217

Merged
jantimon merged 1 commit into
mainfrom
fix/stale-latest-pointer-message
Aug 6, 2026
Merged

Explain a stale latest pointer instead of leaking a raw ENOENT#217
jantimon merged 1 commit into
mainfrom
fix/stale-latest-pointer-message

Conversation

@jantimon

@jantimon jantimon commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Defect

latest resolves through a cwd-keyed pointer file ($XDG_STATE_HOME/wpd/pointers/<hash>.json), so the recording it names can be gone by the time a verb reads it: a cleaned temp dir, a trashed artifact. Every consumer verb then printed the filesystem's own error, which names an internal path and offers no next step:

$ wpd query spans latest
ENOENT: no such file or directory, open '/private/tmp/claude-501/.../repro/rec.json'

Same for assert latest, query blame latest, cpu-diff latest latest, and a run-group whose member recording was deleted.

Fix

The check lives in the resolution layer, so every verb gets it:

  • resolve.ts gains requireArtifact(absPath, subject, fix). It rewrites only the not-there family (ENOENT/ENOTDIR) into a message naming which artifact is gone, its path through displayPath (the same way every report shows one), and the recovery. A permission or IO failure keeps its own error, and so does every path the user typed, so a corrupt or wrong-schema file still gets the schema-epoch message.
  • Each artifact names itself: the recording, the run-group manifest, the CPU model / raw profile, the allocation model / raw profile. query cpu latest blames the .cpu.json, not a recording sitting right there.
  • Run-group members go through one checked path helper, requireMemberRecording, used by loadMemberRecording, resolveVerbTarget, the query spans/query span stitches, both assert member reads, and the per-member diff pair. memberRecordingPath stays as the pure path and is no longer exported.
  • assertMemberMode also shows its path through displayPath.

After:

$ wpd query spans latest
The last recording for this directory no longer exists at rec.json. Run `record` again, or pass an explicit file path.

$ wpd query spans latest          # run-group with a deleted member
The recording for run-group member 'node-cpu' no longer exists at g1.json. Re-record the member, or pass an explicit recording path.

Exit status is unchanged (non-zero).

Tests

test/unit/stale-pointer.test.mjs (browser-free, against compiled dist/, XDG_STATE_HOME pointed at a temp dir):

  • the exact stale-recording message from both resolveTarget and resolveConsumption
  • the CPU model and the allocation model naming their own paths
  • a stale group-manifest pointer naming the manifest
  • ENOTDIR (a path under a file) worded as the same not-there case
  • an explicit missing path resolving unchecked, so the reader's own error survives
  • a deleted run-group member, through loadMemberRecording and resolveVerbTarget

Two existing tests pointed latest at files that were never created; they now create them, rather than weakening the new check.

Gates: build, 761 unit tests, lint, format:check, knip all pass. Verified on the real CLI for both the plain and the run-group case.

`latest` resolves through a cwd-keyed pointer file, so the recording it
names can be deleted after the fact (a cleaned temp dir, a trashed
artifact). Every consumer verb then surfaced the filesystem's own
`ENOENT ... open '/private/tmp/...'`, which names an internal path and
gives the caller no next step.

Resolution now checks the artifact before handing its path on:
`requireArtifact` rewrites the not-there family (ENOENT/ENOTDIR) into a
message naming which artifact is gone (recording, run-group manifest,
CPU or allocation profile), its path through `displayPath`, and the fix.
Any other errno, and every path the user typed, keeps its own error.

Run-group members get the same treatment: `requireMemberRecording` is
the one checked path helper every member read goes through, so the
stitching verbs and the routed single-member verbs name the member
rather than the errno.
@jantimon
jantimon merged commit f541c48 into main Aug 6, 2026
4 checks passed
@jantimon
jantimon deleted the fix/stale-latest-pointer-message branch August 6, 2026 07:30
@github-actions github-actions Bot mentioned this pull request Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant