Skip to content

fix(#265): create screenshot target directory before capture — stop blaming device state#283

Merged
Lykhoyda merged 2 commits into
mainfrom
fix/265-screenshot-missing-dir
Jun 12, 2026
Merged

fix(#265): create screenshot target directory before capture — stop blaming device state#283
Lykhoyda merged 2 commits into
mainfrom
fix/265-screenshot-missing-dir

Conversation

@Lykhoyda

Copy link
Copy Markdown
Owner

Summary

device_screenshot with a path under a non-existent directory failed with "The device may be transitioning state (booting, OOM, locked)" — a pure misdiagnosis of a filesystem precondition (GH #265).

Root cause (live-verified on a healthy booted simulator): xcrun simctl io <udid> screenshot docs/proof/<missing>/01.jpg exits 4 with NSCocoaErrorDomain "The folder doesn't exist" (underlying ENOENT). The iOS capturer's bare catch { return false } erased the cause, and rawResultFail mapped every non-no-device failure to the device-state guess. The Android tier had the identical hazard via createWriteStream(path) ENOENT.

Fix

All at the shared funnel captureAndResizeScreenshot (covers device_screenshot, device_batch auto-captures, and proof_step):

  • mkdir -p the parent of the derived path before any dispatch tier runs (simctl raw, rn-fast-runner, agent-device daemon/CLI, adb stream). New directories are the expected case — the tool's own EPHEMERAL_PATH advisory steers agents toward fresh docs/proof/<slug>/ paths.
  • Honest error when mkdir fails (file blocking an intermediate segment, permissions): short-circuits before any device probing with SCREENSHOT_FAILED / reason: 'target-dir-unavailable' naming the offending path.
  • ~/ expansion (codex review finding on the first commit): Node never expands ~, so the mkdir precondition would have turned a loud failure for ~/Desktop/x.jpg into a silent mislanding (literal ./~/Desktop/ under the bridge cwd, success reported). deriveScreenshotPath now expands a leading ~/ to homedir() and refuses unexpandable forms (~user/..., bare ~). The traversal guard runs on raw input before expansion, so ~/../ cannot smuggle a collapsed .. past it.

Verification

  • TDD throughout: 6 new tests, each watched fail first (the misdiagnosis test pins doesNotMatch /transitioning/i against the exact production message).
  • Full unit suite: 2019/2019 (clean main baseline re-measured at 2013 in a fresh worktree).
  • Live gate: the exact issue repro (platform=ios, path under a fresh nested dir) now captures + resizes end-to-end against the booted simulator (1206×2622 → 800×1739, 48% bytes saved).
  • Multi-LLM review: Antigravity SHIP; Codex NEEDS-WORK → its one IMPORTANT finding (tilde mislanding) verified real and fixed in the second commit. Codex's pre-existing device_record advisory filed as fast-follow bug: device_record has the same missing-target-directory hazard fixed for device_screenshot in #265 #282.

Closes #265

🤖 Generated with Claude Code

Lykhoyda and others added 2 commits June 12, 2026 13:01
…laming device state

device_screenshot with a path under a non-existent directory failed with
"The device may be transitioning state (booting, OOM, locked)" — a pure
misdiagnosis. Live-verified: `xcrun simctl io <udid> screenshot
docs/proof/<missing>/01.jpg` exits 4 with NSCocoaErrorDomain "The folder
doesn't exist" (underlying ENOENT) on a healthy booted simulator. The
capturer's bare `catch { return false }` erased the cause and
rawResultFail mapped every non-no-device failure to the device-state
guess. Same hazard on Android: createWriteStream(path) ENOENTs into the
identical message.

Fix at the shared funnel (captureAndResizeScreenshot — covers
device_screenshot, device_batch auto-captures, proof_step):
- mkdir -p the parent of the derived path before any dispatch tier runs;
  new directories are the expected case since the EPHEMERAL_PATH advisory
  steers agents toward fresh docs/proof/<slug>/ paths.
- If mkdir itself fails (file blocking an intermediate segment,
  permissions), short-circuit BEFORE any device probing with an honest
  SCREENSHOT_FAILED naming the path (reason: target-dir-unavailable).

Tests: 3 new (raw iOS path repro, mkdir-failure classification with
negative assertion on /transitioning/i, runner-path dir precondition).
Suite: 2016/2016 (main: 2013). Live gate: previously-failing repro now
captures + resizes end-to-end against the booted simulator.

Closes #265

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Node never expands `~`, so the new mkdir-p precondition turned a
previously loud failure for `~/Desktop/x.jpg` into a silent mislanding:
a literal `./~/Desktop/` created under the bridge cwd, success reported,
screenshot nowhere near the user's Desktop.

deriveScreenshotPath now expands a leading `~/` to homedir() so every
consumer (mkdir, advisories, all capture tiers) sees the same real path,
and refuses unexpandable forms (`~user/...`, bare `~`) with an
actionable error instead of mislanding. The traversal guard still runs
on the RAW input before expansion, so `~/../` cannot smuggle a
collapsed `..` past it via join().

Tests: 3 new (expansion, honest refusal, guard-before-expansion pin).
Suite: 2019/2019.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@Lykhoyda Lykhoyda merged commit 81c386a into main Jun 12, 2026
10 checks passed
@Lykhoyda Lykhoyda deleted the fix/265-screenshot-missing-dir branch June 12, 2026 11:15
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.

bug: device_screenshot blames "device transitioning state" when the target directory doesn't exist

1 participant