fix(observability): inject TRA support-file listener synchronously (SDK-7121) - #1160
fix(observability): inject TRA support-file listener synchronously (SDK-7121)#1160osho-20 wants to merge 4 commits into
Conversation
…DK-7121)
setEventListeners deferred the support-file write to an async glob callback
while runs.js proceeded synchronously to md5 hashing and zip archiving. The
injection raced the archive: a lost race shipped an un-instrumented suite, and
md5 caching ("Skipping zip upload...") made the bad zip sticky, so the new
Automate dashboard (TRA) received zero test events while the old dashboard
(independent of the injected plugin) kept working. This is why the symptom was
environment-specific — a slower CI pipeline loses the race.
Switch setEventListeners (and the identical race in the accessibility
setAccessibilityEventListeners glob branch) to glob.sync so the writes complete
before the caller reads the files. Adds a regression test asserting the
observability require is present synchronously after the call returns.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
↻ This verdict comment is the review anchor — it's updated in place on each run (the gate posts its status separately). — SDK PR Review Agent |
Address SDK PR Review coverage-gap finding on the regression test. The fix touched two files (setEventListeners and the accessibility setAccessibilityEventListeners glob branch) but the test only covered the observability path. Add: - accessibility setAccessibilityEventListeners synchronous-injection test (exercises the glob.sync pattern branch), - observability idempotency test (a second call does not double-inject). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pri-gadhiya
left a comment
There was a problem hiding this comment.
Verdict: Approve. Root cause is correctly diagnosed, the fix is minimal and correct, and it ships with a genuine regression guard. I verified the load-bearing claims against the source rather than the description.
Confirmed
- The race is real.
runs.js(216/227/230) calls the void-returning listeners, then flows straight intocheckUploadedMd5→archiver.archive(263, 276) with noawaitin between. The old asyncglob(...cb)fired after the archive had already read the file. glob.syncis the right API —globis pinned^7.2.0;glob.sync(pattern, opts)is a valid v7 signature.- Only
runs.jsconsumes these functions — nothing depended on the async behavior. - a11y fix is correctly scoped —
setAccessibilityEventListeners' non-magic branch was already synchronous; only the magic-pattern branch was async, and that's exactly what's fixed. No residual race. - No other async-glob-then-archive races remain — the only other callback-
globusages are the archive stream itself and the md5 walk. - Error-handling parity holds —
glob.syncthrows instead ofif(err) return, but the outertry/catchcatches anddebug-logs it: equivalent outcome. - Test wiring is sound — both helpers resolve
getSupportFilesfrom the samebin/helpers/helpermodule, so the single sinon stub covers both paths; asserted require strings match the injectors exactly.
Worth adding (positive)
- Self-healing cache.
checkSpecsMd5hashes the whole cypress folder, which includes the support file. Post-fix the file is always instrumented → different md5 → the stale un-instrumented zip is no longer a cache hit → fresh upload. Affected customers recover on their first run with the fixed CLI, no manual cache bust. Worth noting in the release note.
Non-blocking nits
- Idempotency is asserted only for the o11y path; the a11y injector has the same
!includes(...)guard, so adding a symmetric assertion would be nice-to-have. test/unit/bin/testObservability/setEventListeners.jsalso houses the a11y test — fine as an SDK-7121 bundle, but the path implies o11y-only.- Please confirm CI is green before merge (I couldn't run the suite locally). The
patchbump is correct.
| if(err) { | ||
| logger.debug('EXCEPTION IN BUILD START EVENT : Unable to parse cypress support files'); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Since we are removing this, how do we know if exception caused issue in build start?
There was a problem hiding this comment.
Exception visibility is preserved — actually improved. With glob.sync, any glob failure now throws instead of being handed to a callback err arg, and that throw is caught by the outer try…catch(e) wrapping the whole function:
} catch(e) {
logger.debug(`Unable to parse support files to set event listeners with error ${e}`, true, e);
}So a build-start parse failure is still logged — and with more signal than before: the removed callback only logged a static string (EXCEPTION IN BUILD START EVENT : Unable to parse cypress support files) with no error object, whereas the outer catch logs the actual ${e} and forwards true, e to the reporter.
This also brings it in line with testObservability.setEventListeners, which is the pattern this PR mirrors — it uses the same glob.sync + single outer catch and never had a separate glob-error branch either.
There was a problem hiding this comment.
Addressed in code (commit 3de3f02): wrapped glob.sync in its own try/catch that restores the distinctive EXCEPTION IN BUILD START EVENT : Unable to parse cypress support files log on a parse failure (now with the error object attached) and returns early — keeping the call synchronous to avoid the archive race.
…ailure (SDK-7121) Addresses PR #1160 review r3682515873: switching the support-file glob to glob.sync dropped the distinctive 'EXCEPTION IN BUILD START EVENT' log that the removed async callback emitted on a parse error. Wrap glob.sync in its own try/catch to restore that signal (now with the error object attached) while keeping the call synchronous to avoid the archive race. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
The new Automate dashboard (TRA / Test Observability) received no test events for Cypress runs on a customer's DEV pipeline, while the old Automate dashboard worked and the same suite/CLI/packages worked on another environment. Ref: SDK-7121.
Root cause
setEventListeners(bin/testObservability/helper/helper.js) injects the observability plugin into the user's Cypress support file:It did this inside an async
glob(pattern, {}, cb)callback and returned immediately without awaiting it. Inbin/commands/runs.js, the caller proceeds synchronously to md5 hashing (checkUploadedMd5) and zip archiving (archiver.archive) right after the call. So the injection raced the archive:Md5 caching makes a lost race sticky: once a non-instrumented zip is cached under the original-content md5, every subsequent run logs
"Skipping zip upload since BrowserStack already has your test suite..."and reuses the bad zip. The old Automate dashboard is unaffected because it does not depend on the injected plugin — exactly the reported asymmetry.Reproduction
Calling
setEventListenersand reading the support file synchronously afterwards (as md5/archive do):Fix
setEventListeners→ useglob.syncso the support-file writes complete before the function returns.setAccessibilityEventListeners' glob-pattern branch (bin/accessibility-automation/helper.js).Testing
test/unit/bin/testObservability/setEventListeners.js— asserts the observability require is present synchronously after the call. Fails on unfixed code, passes with the fix.accessibility-automation/cypressunit tests: green (6/6).commands/runs.jsunit file shows the same pre-existing standalone failures with and without this change (unrelatedsetUsageReportingFlagassertions).Release
🤖 Generated with Claude Code