fix(core): follow BS App Automate tmp dir relocation for Maestro screenshots#2353
fix(core): follow BS App Automate tmp dir relocation for Maestro screenshots#2353prklm10 wants to merge 1 commit into
Conversation
…enshots
BrowserStack hosts moved the App Automate session directories from
/tmp/{sessionId} to /usr/local/.browserstack/app-automate-tmp/{sessionId},
breaking the Maestro screenshot relay's hardcoded /tmp globs and scope
roots (screenshot lookups 404'd).
Route every BS-mode path through a new appAutomateTmpDir() helper that
reads PERCY_APP_AUTOMATE_TMP_DIR and defaults to
/usr/local/.browserstack/app-automate-tmp, so infra can relocate the
root again without a CLI release. Self-hosted mode is untouched
(PERCY_MAESTRO_SCREENSHOT_DIR).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
prklm10
left a comment
There was a problem hiding this comment.
Claude Code Review (automated) — 5 inline finding(s). Full report in the PR comment below. Verdict: Passed.
| // BrowserStack-mode only — self-hosted scoping stays on | ||
| // PERCY_MAESTRO_SCREENSHOT_DIR. | ||
| export function appAutomateTmpDir() { | ||
| let dir = process.env.PERCY_APP_AUTOMATE_TMP_DIR || '/usr/local/.browserstack/app-automate-tmp'; |
There was a problem hiding this comment.
[Medium] Rollout-skew risk: no legacy /tmp fallback
If any BS host fleet segment (Linux/Android vs macOS/iOS are separate fleets) still writes to /tmp/{sid}, this CLI version globs only the new root and hard-404s — and non-relocated hosts by definition run older injection that won't set PERCY_APP_AUTOMATE_TMP_DIR=/tmp.
Suggestion: Confirm the relocation is fleet-wide for both Android and iOS hosts before release (stragglers can inject PERCY_APP_AUTOMATE_TMP_DIR=/tmp), or add a legacy fallback that retries the /tmp pattern (with matching scopeRoot) when the new-root glob is empty and the env var is unset.
Reviewer: stack:code-reviewer
| // PERCY_MAESTRO_SCREENSHOT_DIR. | ||
| export function appAutomateTmpDir() { | ||
| let dir = process.env.PERCY_APP_AUTOMATE_TMP_DIR || '/usr/local/.browserstack/app-automate-tmp'; | ||
| return dir.replace(/[/\\]+$/, ''); |
There was a problem hiding this comment.
[Low] No shape validation on PERCY_APP_AUTOMATE_TMP_DIR
Asymmetric with PERCY_MAESTRO_SCREENSHOT_DIR, which gets absolute-path + existing-dir checks with actionable 400s. A relative value here yields a cwd-relative glob surfacing only as an opaque 404; / trims to '', silently making the filesystem root the tmp root. Not a security issue (env is host-injected; containment fails closed), but host misconfiguration will be painful to debug.
Suggestion: After trimming, fall back to the default (optionally with a warn log) when the value is empty or not absolute.
Reviewer: stack:code-reviewer
| scopeRoot = platform === 'ios' | ||
| ? `/tmp/${sessionId}` | ||
| : `/tmp/${sessionId}_test_suite`; | ||
| ? `${appAutomateTmpDir()}/${sessionId}` |
There was a problem hiding this comment.
[Low] scopeRoot and glob root resolved by separate env reads
The containment boundary (scopeRoot) and the glob search root come from independent appAutomateTmpDir() calls; a mid-request env change would make them diverge. Divergence fails closed (404), so this is coherence-only.
Suggestion: Optionally resolve the root once in the handler and derive the glob root from the already-passed scopeRoot, as the self-hosted branch does.
Reviewer: stack:code-reviewer
| // Real-fs root (matched by the top-level $bypass) — fast-glob caches its | ||
| // fs bindings on first import, so a memfs-only root created in a later | ||
| // test is invisible to it; real-fs fixtures sidestep the staleness. | ||
| describe('PERCY_APP_AUTOMATE_TMP_DIR override', () => { |
There was a problem hiding this comment.
[Low] Override tests cover the Android branch only
The iOS glob and scopeRoot expressions are never exercised under an override (low risk — same tmpRoot expression as Android).
Suggestion: Optionally add one iOS spec mirroring the Android override test, with a fixture under ${CUSTOM_ROOT}/${SID}/emu_maestro_debug_abc/....
Reviewer: stack:code-reviewer
| expect(payload.tiles[0].content).toBe(Buffer.from('PNGBYTES-CUSTOM-ROOT').toString('base64')); | ||
| }); | ||
|
|
||
| it('tolerates a trailing slash on the override', async () => { |
There was a problem hiding this comment.
[Low] Trailing-slash spec doesn't strictly pin the trim behavior
fast-glob normalizes // in patterns and realpath collapses duplicate separators, so this spec would likely pass even without the replace(/[/\\]+$/, '') trim — it documents intent more than it guards the code.
Suggestion: Optionally add a direct unit assertion on the exported helper, e.g. expect(appAutomateTmpDir()).toBe(CUSTOM_ROOT) with the env set to ${CUSTOM_ROOT}///.
Reviewer: stack:code-reviewer
Claude Code PR ReviewPR: #2353 • Head: f28af57 • Reviewers: stack:code-reviewer SummaryFixes the Maestro screenshot relay after BrowserStack App Automate hosts relocated session artifact directories from Review Table
Findings
Informational (not counted against the verdict): the fixed real-fs fixture name Verdict: PASS — approved by stack:code-reviewer; 1 Medium deployment-verification question (fleet-wide relocation confirmation) and 4 non-blocking polish items. |
Summary
/tmp/{sessionId}to/usr/local/.browserstack/app-automate-tmp/{sessionId}, breaking the Maestro screenshot relay's hardcoded/tmpglobs and scope roots — screenshot lookups started 404'ing.scopeRootused by the realpath containment check) now route through a newappAutomateTmpDir()helper.PERCY_APP_AUTOMATE_TMP_DIRenv var, defaulting to/usr/local/.browserstack/app-automate-tmp, so infra can relocate the root again without a CLI release. Trailing separators are trimmed and backslashes normalized for the glob.PERCY_MAESTRO_SCREENSHOT_DIR.Test plan
/percy/maestro-screenshotspecs updated to the new default root (Android + iOS globs,filePathacceptance/containment, PNG-fill, regions) — all passing.PERCY_APP_AUTOMATE_TMP_DIR overridespecs: globbing under an overridden root (real-fs fixture), trailing-slash tolerance, andfilePathcontainment re-scoping to the overridden root (default-root files 404).packages/core/test/api.test.jsrun locally: 126/127 pass; the single failure (when the server is disabled does not start a server with #start(), ECONNREFUSED vs AggregateError) also fails on a clean master checkout — pre-existing local Node environment issue, unrelated to this change.🤖 Generated with Claude Code