@clipboard-health/ai-rules 2.41.1 → 2.41.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/package.json +1 -1
  2. package/skills/flaky-critic/SKILL.md +41 -2
  3. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/answer-key.json +242 -0
  4. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/cohort.md +37 -0
  5. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/input-hashes.txt +20 -0
  6. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-01.md +141 -0
  7. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-02.md +115 -0
  8. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-03.md +24 -0
  9. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-04.md +85 -0
  10. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-05.md +78 -0
  11. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-06.md +82 -0
  12. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-07.md +128 -0
  13. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-08.md +99 -0
  14. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-09.md +107 -0
  15. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-10.md +141 -0
  16. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-11.md +98 -0
  17. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-12.md +99 -0
  18. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-13.md +25 -0
  19. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-14.md +111 -0
  20. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-15.md +27 -0
  21. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-16.md +46 -0
  22. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-17.md +101 -0
  23. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-18.md +88 -0
  24. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-19.md +98 -0
  25. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/inputs/case-20.md +91 -0
  26. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts/verdicts.jsonl +20 -0
  27. package/skills/flaky-critic/references/backtests/2026-07-16-b8-prior-attempts.md +170 -0
  28. package/skills/flaky-critic/references/rubric.md +17 -2
@@ -0,0 +1,24 @@
1
+ # Historical plan snapshot
2
+
3
+ ## Groundcrew
4
+
5
+ Repository: [admin frontend]
6
+ Implementation workflow: use the `core:cb-work`/`cb-work` skill when available. If that skill is unavailable, follow this repo's [AGENTS.md/CLAUDE.md]([external evidence reference]) implementation workflow and run the documented verification.
7
+
8
+ ## Task
9
+
10
+ A deep dive on the chronic Home Health fullLifecycle flake (fingerprint family 33deef731a10; [ticket family redacted] — recurred after 6+ times) diagnosed the root cause at 4/5 confidence:
11
+
12
+ Every HH action dialog (`CaseClosureDialog`, `CancelVisitDialog`, etc.) renders as a React descendant of a react-query list item (`CaseCard` from `useAgencyCases`, `VisitCard` from `useCaseVisits`). Those lists are invalidated by the dialogs' own mutations (`useUpdateVisit.ts:66`, `useUpdateCase.ts:44-56`) and also refetch on a 5-min `staleTime` / 10-min `refetchInterval` / window-focus cadence. When a refetch resolves while a dialog is open, the list item reconciles ("element is not stable") or drops out of the filtered result ("element was detached from the DOM") — exactly the signatures in [ticket redacted] (detached Close button at `submitCloseCase`) and [ticket redacted] (flapping cancel-dialog title).
13
+
14
+ This ticket is the phase-1 mitigation: while any HH action dialog is open, suppress `refetchOnWindowFocus`/`refetchInterval` on the backing list queries and defer query invalidation until the dialog closes (flush on close). Do NOT add waits, retries, or locator changes to the specs — the 12 prior stabilization commits did that and the flake recurred each time (rubric B4).
15
+
16
+ ## Acceptance Criteria
17
+
18
+ - [ ] While an HH dialog is open, the backing list queries neither interval/focus-refetch nor process invalidations; deferred invalidations flush on dialog close.
19
+ - [ ] Fault-injection validation: force a list refetch while a dialog is open — before the change this reproduces the detached/unstable signature, after it does not. If the environment cannot run e2e against staging, cover with a component-level test that simulates refetch-while-open.
20
+ - [ ] Existing HH unit/component tests and lint/typecheck pass.
21
+
22
+ ## Notes
23
+
24
+ The durable fix (hoisting dialogs into a stable dialog host above `cases.map`) and fullLifecycle spec decomposition are a separate ticket. Full deep-dive document: ask Rocky for `deep-dives/hh-full-lifecycle.md`.
@@ -0,0 +1,85 @@
1
+ # Historical plan snapshot
2
+
3
+ ## Groundcrew
4
+
5
+ Repository: [mobile frontend]
6
+ Implementation workflow: implement this ticket using the `cb-work` skill. If `cb-work` is unavailable, use the `core:go` skill.
7
+
8
+ ## Context
9
+
10
+ Implementation ticket created from burst investigation `[ticket redacted]`.
11
+
12
+ Run: [[external evidence reference]]([external evidence reference])
13
+ Repo: [mobile frontend]
14
+ Workflow: run [reference redacted]`/ attempt`1`Branch:`main`Commit:`d0cd1ee0995e857cb2aed4d2a70c904dccef8044`First failure:`2026-06-27T06:35:06Z`Last failure:`2026-06-27T06:35:06Z`
15
+ Tests bundled: 4
16
+
17
+ ## Contained Fingerprints
18
+
19
+ - `efff313a6fc2` — Documents should upload documents — `playwright/e2e/documents/documents.spec.ts:20`
20
+ - `f9aa7c56171d` — Login via email link > Cognito should log in via Cognito magic link from email — `playwright/e2e/auth/authByEmail.spec.ts:35`
21
+ - `12dc0db8842a` — Shift Block Cancellation should allow cancellation of a booked block — `playwright/e2e/shiftBlocks/cancelBlocks.spec.ts:50`
22
+ - `c97ce093be4e` — Shift Bookability should redirect to onboarding payments when agent payments are disabled and agent can onboard to stripe — `playwright/e2e/shift/shiftBookability.spec.ts:234`
23
+
24
+ ## Investigation Findings
25
+
26
+ Shared cause identified: all four flakes failed before their product test bodies while the shared Playwright `page` fixture loaded `/v2/e2e-test/loading`. The browser received sustained staging `.js` responses from CloudFront with HTTP `503` and `x-cache: LimitExceeded from cloudfront`, so React never mounted and `getByText("Hang in there!")` never became visible.
27
+
28
+ Runner-level check: not a single-runner failure. Shards ran on distinct self-hosted runners and completed their jobs successfully:
29
+
30
+ - `e2e-playwright-1`: `cbh-16-cores_i-0e438393bf84b533b`
31
+ - `e2e-playwright-2`: `cbh-16-cores_i-0043fa03fe51d6c6e`
32
+ - `e2e-playwright-3`: `cbh-16-cores_i-0409bc9fd01775a33`
33
+ - `e2e-playwright-4`: `cbh-16-cores_i-0f8a8d240a52a828f`
34
+
35
+ Time correlation: the failing attempts saw first staging script failures within a tight window, `2026-06-27T06:35:08.430Z` through `2026-06-27T06:35:08.536Z`. The last observed script failures were `2026-06-27T06:36:05.879Z` through `2026-06-27T06:36:06.095Z`.
36
+
37
+ Shared dependency: all regular E2E tests use `playwright/setup/global.ts`, which mocks required services and then calls `bootstrapAppRouteWithStaticAssetRetries` from `playwright/helpers/staticAssets.ts` for `appPaths.e2eTestLoading`. The four tests surfaced the same shared bootstrap failure, not four independent product-flow failures.
38
+
39
+ LLM report summary: `16` tests total, `12` passed, `4` flaky, `0` failed. Every contained test failed attempt 1 after about `61s`, then passed on retry in `15-29s`.
40
+
41
+ Failure details from the report and job logs:
42
+
43
+ - `Login via email link > Cognito`: helper diagnostics recorded `744` staging script failures; structured report retained `268` HTTP 503 staging `.js` instances.
44
+ - `Documents > should upload documents`: `738` staging script failures; retained `251` HTTP 503 staging `.js` instances.
45
+ - `Shift Bookability > onboarding payments`: `602` staging script failures; retained `228` HTTP 503 staging `.js` instances.
46
+ - `Shift Block Cancellation`: `698` staging script failures; retained `246` HTTP 503 staging `.js` instances.
47
+ - Each failed bootstrap attempt issued `983` script requests; each failed test had two bootstrap attempts and `2964` observed network instances in the reporter summary.
48
+ - Error headers included CloudFront diagnostics such as `server: CloudFront`, `x-amz-cf-pop: HIO52-P4`, and `x-cache: LimitExceeded from cloudfront`.
49
+ - Page diagnostics showed `document.readyState=complete` and body text from the pre-React splash script, consistent with the HTML document loading while JS bundles failed.
50
+
51
+ Preflight evidence: both the upstream staging asset preflight and shard-local preflights passed before the burst. Example artifacts:
52
+
53
+ - `staging-mobile-assets-preflight.json`: `failureCount: 0`, `totalCheckedCount: 1218`, stable window `2026-06-27T06:32:20.671Z` to `2026-06-27T06:33:23.895Z`.
54
+ - `deployed-mobile-assets-preflight-shard-1.json`: `failureCount: 0`, `totalCheckedCount: 1218`, stable window ended `2026-06-27T06:34:59.254Z`.
55
+ - `deployed-mobile-assets-preflight-shard-3.json`: `failureCount: 0`, `totalCheckedCount: 1218`, stable window ended `2026-06-27T06:34:59.254Z`.
56
+
57
+ Prior related fixes: `[ticket redacted]` and `[ticket redacted]` are already completed and this June 27 recurrence happened after those changes. Current workflow still runs upstream plus shard-local asset preflight, and both passed in this run, so the next fix should address the browser bootstrap/static-asset fanout or CloudFront limit behavior directly. No open GitHub change with label `flaky-test-fix`, no open change matching `e2e-test/loading`, `bootstrapAppRouteWithStaticAssetRetries`, `staticAssets`, `flaky`, or `deflake` was found.
58
+
59
+ Confidence: 5/5 for the immediate shared failure mechanism. The upstream reason CloudFront emitted `LimitExceeded` is still inferred from headers and timing, so implementation should preserve or improve edge diagnostics.
60
+
61
+ ## Requested Fix
62
+
63
+ Fix the recurrent staging E2E `/v2/e2e-test/loading` bootstrap failure where concurrent browser bootstraps request hundreds of JS assets and CloudFront returns `503 LimitExceeded`.
64
+
65
+ Suggested direction:
66
+
67
+ 1. Measure the effective staging E2E bootstrap concurrency: `fullyParallel: true`, four matrix shards, default Playwright worker count on 16-core runners, and the observed `983` script requests per bootstrap attempt.
68
+ 2. Implement the smallest harness or CI change that prevents simultaneous browser bootstraps from hammering staging static asset delivery. Reasonable options include explicit lower Playwright `--workers` for staging critical E2E, `strategy.max-parallel` or startup jitter for shards, or a deterministic bootstrap gate/throttle. Prefer a measured concurrency/fanout reduction over adding more retries.
69
+ 3. If a code-side reduction is practical, reduce the number of JS chunks loaded by `/v2/e2e-test/loading`, or add a lighter app-ready route that still verifies the real app bundle can mount before product tests proceed.
70
+ 4. Keep or extend diagnostics for CloudFront `LimitExceeded`, `scriptRequestCount`, first/last failure times, and response headers. Do not fix this by editing the four product specs; they share the same setup failure.
71
+ 5. Re-evaluate whether shard-local mobile asset preflight should remain in addition to the upstream staging preflight. This run had upstream plus per-shard preflights and all passed before browser bootstrap still failed.
72
+
73
+ ## Acceptance Criteria
74
+
75
+ - The fix targets shared bootstrap/static-asset delivery, not the individual product tests.
76
+ - Staging E2E startup no longer launches an avoidable burst of simultaneous `/v2/e2e-test/loading` app bootstraps that can request thousands of JS assets at once.
77
+ - CI artifacts or logs make the chosen concurrency/fanout behavior visible enough to verify on future runs.
78
+ - Existing CloudFront/script failure diagnostics remain at least as informative as they are now.
79
+ - Verification covers changed workflow, Playwright config, helper, or deploy-verifier files as applicable.
80
+
81
+ ## Required change / Commit Instructions
82
+
83
+ - Include this implementation ticket ID in the change body.
84
+ - Include the contained fingerprints in the change or commit context: `efff313a6fc2`, `f9aa7c56171d`, `12dc0db8842a`, `c97ce093be4e`.
85
+ - Implement this ticket using the `cb-work` skill or, if unavailable, the `core:go` skill.
@@ -0,0 +1,78 @@
1
+ # Historical plan snapshot
2
+
3
+ ## Implementation instructions
4
+
5
+ Implement or adopt the plan below in `[admin frontend]` using the `cb-work` skill or, if unavailable, the `core:go` skill.
6
+
7
+ Required workflow:
8
+
9
+ - Include this implementation ticket ID in the change body.
10
+ - Implement this plan using the `cb-work` skill or, if unavailable, the `core:go` skill.
11
+
12
+ ## Flake details
13
+
14
+ ```json
15
+ {
16
+ "repo": "[admin frontend]",
17
+ "test": "Schedule rate negotiations (redesign) — desktop workplace user accepts one rate proposal and ends another from the shift details drawer",
18
+ "file": "playwright/e2e/rateNegotiation.spec.ts:40",
19
+ "framework": "playwright",
20
+ "isNewFlaky": false,
21
+ "failures": [
22
+ {
23
+ "error": "Error: Failed to create agent after 1 attempt.\nDiagnostics: {\"historicalCorrelationKey\":\"[correlation redacted]\",\"correlationId\":\"[correlation redacted]\",\"githubRunId\":\"28413169950\",\"githubRunAttempt\":\"1\",\"githubJob\":\"e2e-tests-staging\",\"shardIndex\":\"3\",\"shardTotal\":\"4\",\"workerIndex\":\"0\",\"parallelIndex\":\"0\",\"pid\":62278,\"stepName\":\"hcp_creation\",\"stepStatus\":\"failure\",\"durationMs\":55,\"requestMethod\":\"POST\",\"requestUrl\":\"[external evidence reference]",\"requestService\":\"api\",\"status\":400,\"responseShape\":{\"error\":\"string\",\"message\":\"string\",\"statusCode\":\"number\"},\"traceparent\":\"00-0000000000000000688e121ea4b1822a-3a53dc9499469a1c-01\",\"apiGatewayRequestId\":\"fwBzChg3vHcEP5A=\",\"sanitizedResponseMessage\":\"Phone already in use\\nBad Request\",\"errorName\":\"AxiosError\",\"errorMessage\":\"Request failed with status code 400\",\"errorCode\":\"ERR_BAD_REQUEST\"}",
24
+ "stack": "Error: Failed to create agent after 1 attempt.\nDiagnostics: {\"historicalCorrelationKey\":\"[correlation redacted]\",\"correlationId\":\"[correlation redacted]\",\"githubRunId\":\"28413169950\",\"githubRunAttempt\":\"1\",\"githubJob\":\"e2e-tests-staging\",\"shardIndex\":\"3\",\"shardTotal\":\"4\",\"workerIndex\":\"0\",\"parallelIndex\":\"0\",\"pid\":62278,\"stepName\":\"hcp_creation\",\"stepStatus\":\"failure\",\"durationMs\":55,\"requestMethod\":\"POST\",\"requestUrl\":\"[external evidence reference]",\"requestService\":\"api\",\"status\":400,\"responseShape\":{\"error\":\"string\",\"message\":\"string\",\"statusCode\":\"number\"},\"traceparent\":\"00-0000000000000000688e121ea4b1822a-3a53dc9499469a1c-01\",\"apiGatewayRequestId\":\"fwBzChg3vHcEP5A=\",\"sanitizedResponseMessage\":\"Phone already in use\\nBad Request\",\"errorName\":\"AxiosError\",\"errorMessage\":\"Request failed with status code 400\",\"errorCode\":\"ERR_BAD_REQUEST\"}\n at /opt/actions-runner/_work/[admin frontend]/[admin frontend]/playwright/api/createAgent.ts:152:13\n at /opt/actions-runner/_work/[admin frontend]/[admin frontend]/playwright/e2e/rateNegotiation.spec.ts:84:24\n at WorkerMain._runTest (/opt/actions-runner/_work/[admin frontend]/[admin frontend]/node_modules/dd-trace/packages/datadog-instrumentations/src/playwright.js:1692:5)",
25
+ "branch": "main",
26
+ "pipeline": "[external evidence reference]",
27
+ "commit": "21ec4288614efa471d7bed32665e198ef06b313d",
28
+ "durationMs": 8519,
29
+ "shard": "3/4",
30
+ "timestamp": "2026-06-30T01:05:53Z"
31
+ }
32
+ ]
33
+ }
34
+ ```
35
+
36
+ ## Plan output
37
+
38
+ **Test ID:** `2f8cacf23bff`
39
+
40
+ **Confidence:** 5/5. The failure response says `Phone already in use`, the shard log shows `POST /api/user/create` returned HTTP 400 during `hcp_creation`, and the local retry classifier bails on that exact 400 instead of using the already-existing fresh-identity retry loop.
41
+
42
+ **Failure surface:** Test setup/auth/data.
43
+
44
+ **Current main status:** The failing commit `21ec4288614e` already included the prior rate-negotiation setup hardening (`[ticket redacted]`, commit `03a650774a2`) and license auth retry hardening (`[ticket redacted]`, commit `9264bcc2d62`). Current `origin/main` still does not include change [reference redacted] (`fc06672eaa4`), which directly addresses this phone-collision path.
45
+
46
+ **Symptom:** The first Playwright attempt failed before browser flow with `Failed to create agent after 1 attempt`; the retry passed. The available LLM report for run [reference redacted]` has one flaky test, this spec, with failed attempt duration about 8.5s and a passing retry around 28s.
47
+
48
+ **Root cause:** `Agent.createAgentWithRetry()` generates a fresh worker phone/email inside each retry attempt, but `getAgentSetupRetryClassification()` classifies all unrecognized HTTP 400 setup failures as `non_retryable_agent_setup_failure`. The backend returned a deterministic setup-data collision, `Phone already in use`, so the helper called `bail()` after the first attempt and never tried the next generated identity.
49
+
50
+ **Evidence:**
51
+
52
+ - LLM report summary: 53 total tests, 50 passed, 1 flaky, 0 failed; the only flaky test is `playwright/e2e/rateNegotiation.spec.ts:40`.
53
+ - Failed attempt timeline has no app network and a blank screenshot, confirming pre-browser setup failure.
54
+ - Shard log for job [reference redacted]`at`2026-06-30T01:06:01Z`: `POST [external evidence reference] 400`, response body `{"statusCode":400,"message":"Phone already in use","error":"Bad Request"}`.
55
+ - Setup diagnostics: `stepName=hcp_creation`, `stepStatus=failure`, `status=400`, `traceparent=00-0000000000000000688e121ea4b1822a-3a53dc9499469a1c-01`, `apiGatewayRequestId=fwBzChg3vHcEP5A=`, `sanitizedResponseMessage="Phone already in use\nBad Request"`.
56
+ - Code path: `playwright/e2e/rateNegotiation.spec.ts:83-84` calls `agentInstance1.createAgentWithRetry()` and `agentInstance2.createAgentWithRetry()`; `playwright/api/createAgent.ts` catches setup errors and bails when `getAgentSetupRetryClassification()` returns `non_retryable_agent_setup_failure`; `playwright/helpers/createAgentRetryClassification.ts` lacks a retryable phone-collision branch on current `origin/main`.
57
+ - Existing fix found: change [reference redacted] / commit `fc06672eaa4` adds `retryable_phone_collision` for HTTP 400 `Phone already in use` and a Jest regression test.
58
+
59
+ **Proposed fix:** Test harness fix. Land change [reference redacted] or implement the equivalent small change in `playwright/helpers/createAgentRetryClassification.ts` and `scripts/createAgentRetryClassification.test.ts`: classify HTTP 400 setup errors whose sanitized response message includes `Phone already in use` as retryable. Prefer constraining the check to `hcp_creation` if updating the change, because the retry is safe specifically when the helper will generate a fresh worker identity on the next attempt.
60
+
61
+ **Observability to reach 5/5:** N/A -- confidence is 5/5. The existing setup diagnostics already include lifecycle step, request URL/service, status, sanitized response message, traceparent, and API Gateway request ID.
62
+
63
+ **Sibling candidates:**
64
+
65
+ - `playwright/e2e/licenses.spec.ts` and `playwright/e2e/legacy/documents/documents.spec.ts` use the same class-based `Agent` helper, so they will benefit from the shared classifier fix.
66
+ - `playwright/api/agent.ts` helper functions already retry `createAgent` with a fresh generated phone and do not use this stricter classifier, so they are not the immediate broken path.
67
+ - `playwright/api/facility.ts` has separate HCP setup paths using `generatePhoneNumber()`; leave as follow-up only if future failures show the same `Phone already in use` signature there.
68
+
69
+ **Validation plan:**
70
+
71
+ - `npx jest scripts/createAgentRetryClassification.test.ts --runInBand`
72
+ - `npm run lint:fast -- playwright/helpers/createAgentRetryClassification.ts scripts/createAgentRetryClassification.test.ts`
73
+ - `npm run typecheck`
74
+ - Verify change CI stays green, especially the Playwright shard containing `playwright/e2e/rateNegotiation.spec.ts`.
75
+
76
+ **Open questions:** None blocking. If updating change [reference redacted], decide whether to narrow the phone-collision classifier to `hcp_creation` before integration.
77
+
78
+ **Residual risk:** A generated phone can collide repeatedly if staging has accumulated enough historical test users, but five fresh attempts make this specific flake unlikely. A longer-term hardening would increase worker identity entropy or allocate test phone ranges deterministically per run/shard.
@@ -0,0 +1,82 @@
1
+ # Historical plan snapshot
2
+
3
+ ## Source investigation
4
+
5
+ Created from [ticket redacted].
6
+
7
+ ## Flake details
8
+
9
+ ```json
10
+ {
11
+ "repo": "[mobile frontend]",
12
+ "test": "Timekeeping should be able to clock in and out for a shift",
13
+ "file": "playwright/e2e/shift/timekeeping.spec.ts:627",
14
+ "framework": "playwright",
15
+ "isNewFlaky": false,
16
+ "failures": [
17
+ {
18
+ "error": "Error: expect(locator).toBeVisible() failed\n\nLocator: getByRole('heading', { name: 'Bookings', exact: true })\nExpected: visible\nTimeout: 30000ms\nError: element(s) not found\n\nCall log:\n - Expect \"toBeVisible\" with timeout 30000ms\n - waiting for getByRole('heading', { name: 'Bookings', exact: true })\n",
19
+ "stack": "Error: expect(locator).toBeVisible() failed\n\nLocator: getByRole('heading', { name: 'Bookings', exact: true })\nExpected: visible\nTimeout: 30000ms\nError: element(s) not found\n\nCall log:\n - Expect \"toBeVisible\" with timeout 30000ms\n - waiting for getByRole('heading', { name: 'Bookings', exact: true })\n\n at /home/runner/work/[mobile frontend]/[mobile frontend]/playwright/e2e/shift/timekeeping.spec.ts:638:80\n at WorkerMain._runTest (/home/runner/work/[mobile frontend]/[mobile frontend]/node_modules/dd-trace/packages/datadog-instrumentations/src/playwright.js:1692:5)",
20
+ "branch": "gh-readonly-queue/main/change-[reference redacted]-ef0a31973a7d405b4a94d2e4be21f3e74289b490",
21
+ "pipeline": "[external evidence reference]",
22
+ "commit": "c353f2b925c99717b95cf9dd5afe4656750a8f4e",
23
+ "durationMs": 88060,
24
+ "shard": "3/4",
25
+ "timestamp": "2026-07-06T16:36:30Z"
26
+ }
27
+ ]
28
+ }
29
+ ```
30
+
31
+ ## Plan output
32
+
33
+ **Test ID:** `7bc4bfd31136`; `playwright/e2e/shift/timekeeping.spec.ts:627`, `Timekeeping > should be able to clock in and out for a shift`
34
+
35
+ **Confidence:** 4/5. The CI report directly shows the app-level crash screen and the `@react-google-maps/api` invariant before the Bookings assertion fails. The remaining inference is that a provider-local fallback or deterministic Maps mock fully removes the CI race across all affected routes.
36
+
37
+ **Failure surface:** App bootstrap / product render, with a third-party SDK test-harness contributor. The Bookings locator failure is downstream of the app-wide error boundary.
38
+
39
+ **Current main status:** The relevant code path still exists on current `origin/main` after refresh. `src/app/app.tsx` still wraps the whole app in `GoogleMapsSdkProvider` and `GoogleMapProvider`; `src/appV2/lib/GoogleMaps/context.ts` still calls `useLoadScript` directly. No open `flaky-test-fix` change matched `timekeeping.spec.ts`/this test. Recent `[ticket redacted]` / change [reference redacted] touched shift readiness helpers, not the Google Maps provider or this timekeeping test.
40
+
41
+ **Symptom:** After `initializeAndLogin` and `page.goto(appPaths.myShiftsV2)`, `await expect(page.getByRole("heading", { name: "Bookings", exact: true })).toBeVisible()` timed out. The failure screenshot showed the generic app crash page: “Oops! Something went wrong.” with `Restart App` and `Contact Support` actions.
42
+
43
+ **Root cause:** The app loads the Google Maps JavaScript SDK globally for every route through `GoogleMapsSdkProvider`, even routes like My Shifts that do not need map UI. In the failed attempt, `@react-google-maps/api` marked the script as loaded while `window.google` was absent and threw: `useLoadScript was marked as loaded, but window.google is not present. Something went wrong.` Because this provider sits under the top-level app `ErrorBoundary`, the whole app rendered the generic crash screen, so the Bookings heading never appeared.
44
+
45
+ **Evidence:**
46
+
47
+ - LLM report from GitHub Actions run [reference redacted]`, shard `3/4`: one flaky test, first attempt failed in 87.4s, retry passed in 42.5s.
48
+ - Failed attempt timeline: `Navigate to "/home-v2/myShifts"` at ~46,020ms, `GET [external evidence reference] returned 200 at ~46,661ms, then console error at ~54,891ms: `Invariant Violation: useLoadScript was marked as loaded, but window.google is not present.`
49
+ - Failed attempt network/lifecycle artifact: `GET [external evidence reference] appeared around ~46,491ms with status `-1`, followed by many aborted app requests and then the invariant. This points to a Google Maps SDK loader failure during app bootstrap/render, not a backend My Shifts response mismatch.
50
+ - Screenshot artifact: generic `Oops! Something went wrong.` error boundary page at assertion failure.
51
+ - Passing retry timeline: Bookings heading visible at ~23,098ms, then the test completed the clock-in, clock-out, timesheet submission, and post-timesheet review flow.
52
+ - Code path: `src/app/app.tsx` wraps all routes with `GoogleMapsSdkProvider`; `src/appV2/lib/GoogleMaps/context.ts` calls `useLoadScript`; `node_modules/react-google-maps/api/src/useLoadScript.tsx` throws the exact invariant when `isLoaded` is true but `window.google` is missing.
53
+
54
+ **Proposed fix:** Product fix first, with a small test-harness hardening if needed.
55
+
56
+ 1. Refactor `src/appV2/lib/GoogleMaps/context.ts` so Google Maps SDK loader failures degrade to `{ isSdkLoaded: false, sdkLoadError }` instead of escaping to the app-wide `ErrorBoundary`.
57
+ - Preserve the exported `GoogleMapsSdkProvider` and `useGoogleMapsSdkContext` API so `src/app/context/googleMapContext.tsx` and map components do not need broad changes.
58
+ - A practical shape is an explicit React context plus an inner loader component that calls `useLoadScript`, wrapped by a provider-local `react-error-boundary` fallback. The fallback should provide the same context value with `isSdkLoaded: false` and a normalized `sdkLoadError`, then still render children.
59
+ - Log the provider-level loader/invariant failure through `APP_V2_APP_EVENTS.GOOGLE_MAPS_SDK_LOAD_ERROR` with error metadata. Do not log the API key.
60
+ - Keep actual map surfaces protected by their existing `GoogleMapErrorBoundary` so map UI can show map-specific fallback copy while non-map routes continue working.
61
+ 2. Add a focused Vitest test under `src/appV2/lib/GoogleMaps/` proving that when the Maps loader throws, descendants still render and `useGoogleMapsSdkContext()` reports an unloaded/error state instead of crashing the app.
62
+ 3. If the targeted Playwright run still contacts external Google Maps or remains flaky after the provider fix, add a central Playwright mock in `playwright/mocks` and call it from `playwright/setup/global.ts` before `bootstrapAppRouteWithStaticAssetRetries`. The mock should fulfill `[external evidence reference] with a minimal SDK stub that defines the namespaces used by app bootstrap and invokes the configured callback, avoiding third-party script races in E2E.
63
+
64
+ **Observability to reach 5/5:** Add provider-level logging for loader invariant failures with route/path, script `data-state`, whether `window.google` and `window.google.maps` exist, and the sanitized SDK URL shape/version/libraries. This would distinguish CDN/network/script races from application code removing the global.
65
+
66
+ **Sibling candidates:** This is provider-wide. Any E2E test that boots the app can hit it, especially tests that navigate to `appPaths.myShiftsV2` and wait for `Bookings`: `playwright/e2e/shift/timekeeping.spec.ts` has multiple such assertions, plus `playwright/e2e/shift/scheduledBookingCard.spec.ts`, `playwright/e2e/homeHealth/workerVisitLifecycle.spec.ts`, `playwright/e2e/homeHealth/workerAcceptsVisitInvite.spec.ts`, and `playwright/e2e/auth/authByEmail.spec.ts`.
67
+
68
+ **Validation plan:**
69
+
70
+ - Run the new focused Vitest file with `npx vitest run --config ./src/appV2/vitest.config.ts src/appV2/lib/GoogleMaps/<new-test-file>.test.tsx`.
71
+ - Run the targeted E2E when Playwright env vars are available: `npx playwright test playwright/e2e/shift/timekeeping.spec.ts:627 --project "Mobile Chrome" --config ./playwright.config.ts`.
72
+ - Run repo verification appropriate for the final diff, at minimum the touched test suite and `npm run verify` if the branch is being finalized.
73
+
74
+ **Open questions:** None before implementation. Prefer the provider-local degradation fix over changing the Bookings assertion or adding a longer wait, because the page is on the app crash screen.
75
+
76
+ **Residual risk:** If Google Maps is actually required during a later step of a different E2E, the provider fallback alone preserves app rendering but map-specific UI may still need a deterministic Playwright SDK stub.
77
+
78
+ ## Implementation instructions
79
+
80
+ - Implement this plan using the `cb-work` skill, or the `core:go` skill if `cb-work` is unavailable.
81
+ - Include this implementation ticket ID in the change body.
82
+ - Keep the fix scoped; do not change the Timekeeping test assertion into a longer timeout/retry because the observed failure is an app crash before Bookings can render.
@@ -0,0 +1,128 @@
1
+ # Historical plan snapshot
2
+
3
+ ## Groundcrew
4
+
5
+ Repository: `[admin frontend]`
6
+ Implementation workflow: use the `core:go` skill.
7
+
8
+ ## Task
9
+
10
+ Fix the My Account Change Password Playwright flake by preventing or clearly diagnosing staging frontend deploy drift for source-coupled E2E runs.
11
+
12
+ The current failure is not the prior [ticket redacted]/[ticket redacted] `firebaseId` response-schema issue. In this run, `/my-account` rendered successfully with the authenticated app shell, but the page snapshot showed only the `Edit` action in Personal Information. The expected `Change password` button was absent even though commit `673813bc6603911dcd8bb3dff83edbd96585c5a8` bakes the Change Password action on unconditionally.
13
+
14
+ ## Flake Details
15
+
16
+ ```json
17
+ {
18
+ "repo": "[admin frontend]",
19
+ "test": "My Account (redesign) — desktop user changes password from the Change Password dialog and sees the success toast",
20
+ "file": "playwright/e2e/myAccount.spec.ts:88",
21
+ "framework": "playwright",
22
+ "isNewFlaky": false,
23
+ "failures": [
24
+ {
25
+ "error": "TimeoutError: locator.click: Timeout 30000ms exceeded.\nCall log:\n - waiting for getByRole('button', { name: 'Change password' })\n",
26
+ "stack": "TimeoutError: locator.click: Timeout 30000ms exceeded.\nCall log:\n - waiting for getByRole('button', { name: 'Change password' })\n\n at /opt/actions-runner/_work/[admin frontend]/[admin frontend]/playwright/e2e/myAccount.spec.ts:99:67\n at WorkerMain._runTest (/opt/actions-runner/_work/[admin frontend]/[admin frontend]/node_modules/dd-trace/packages/datadog-instrumentations/src/playwright.js:1692:5)",
27
+ "branch": "main",
28
+ "pipeline": "[external evidence reference]",
29
+ "commit": "673813bc6603911dcd8bb3dff83edbd96585c5a8",
30
+ "durationMs": 50682,
31
+ "shard": "3/4",
32
+ "timestamp": "2026-06-04T14:55:30Z"
33
+ },
34
+ {
35
+ "error": "TimeoutError: locator.click: Timeout 30000ms exceeded.\nCall log:\n - waiting for getByRole('button', { name: 'Change password' })\n",
36
+ "stack": "TimeoutError: locator.click: Timeout 30000ms exceeded.\nCall log:\n - waiting for getByRole('button', { name: 'Change password' })\n\n at /opt/actions-runner/_work/[admin frontend]/[admin frontend]/playwright/e2e/myAccount.spec.ts:99:67\n at WorkerMain._runTest (/opt/actions-runner/_work/[admin frontend]/[admin frontend]/node_modules/dd-trace/packages/datadog-instrumentations/src/playwright.js:1692:5)",
37
+ "branch": "main",
38
+ "pipeline": "[external evidence reference]",
39
+ "commit": "673813bc6603911dcd8bb3dff83edbd96585c5a8",
40
+ "durationMs": 52505,
41
+ "shard": "3/4",
42
+ "timestamp": "2026-06-04T14:54:36Z"
43
+ },
44
+ {
45
+ "error": "TimeoutError: locator.click: Timeout 30000ms exceeded.\nCall log:\n - waiting for getByRole('button', { name: 'Change password' })\n",
46
+ "stack": "TimeoutError: locator.click: Timeout 30000ms exceeded.\nCall log:\n - waiting for getByRole('button', { name: 'Change password' })\n\n at /opt/actions-runner/_work/[admin frontend]/[admin frontend]/playwright/e2e/myAccount.spec.ts:99:67\n at WorkerMain._runTest (/opt/actions-runner/_work/[admin frontend]/[admin frontend]/node_modules/dd-trace/packages/datadog-instrumentations/src/playwright.js:1692:5)",
47
+ "branch": "main",
48
+ "pipeline": "[external evidence reference]",
49
+ "commit": "673813bc6603911dcd8bb3dff83edbd96585c5a8",
50
+ "durationMs": 54791,
51
+ "shard": "3/4",
52
+ "timestamp": "2026-06-04T14:53:40Z"
53
+ }
54
+ ]
55
+ }
56
+ ```
57
+
58
+ ## Investigation Plan Output
59
+
60
+ **Test ID:** `09855dc59185`; LLM report test id `eafff14ebc7fdc0b7350-40c6230c6fa0783ea922`
61
+
62
+ **Confidence:** 4/5. The artifacts directly show the button absent while the page is otherwise rendered. The one inferred link is the exact deployed frontend build/version because the workflow did not assert or record the expected deployed commit for this manual staging run.
63
+
64
+ **Failure surface:** CI/workflow setup plus assertion/locator symptom. The user action never starts because the target UI element is absent before the click.
65
+
66
+ **Current main status:** The failing commit is current `main` in this worktree and the source code still includes the Change Password action. Commit `673813bc6603911dcd8bb3dff83edbd96585c5a8` specifically removed the `isChangePasswordEnabled` flag gate and made the button unconditional. No open change labeled `flaky-test-fix` matched `myAccount.spec.ts` or this test.
67
+
68
+ **Symptom:** `page.getByRole("button", { name: "Change password" }).click()` timed out for 30 seconds at `playwright/e2e/myAccount.spec.ts:99` on all three attempts.
69
+
70
+ **Root cause:** The staging E2E workflow was run with `workflow_dispatch` and no `expected_deployed_commit_sha`, so `.github/actions/playwright-tests` skipped the existing `playwright:assert-deployed-commit` guard. The test checked out commit `673813bc6603911dcd8bb3dff83edbd96585c5a8`, whose source expects `Change password` to always render, while the browser exercised `[external evidence reference] without verifying that staging served that same commit. The failure artifact matches an older or divergent frontend contract where the Change Password action is still hidden/gated: My Account rendered with `Edit`, account details, notification preferences, and no `Change password` button.
71
+
72
+ **Evidence:**
73
+
74
+ - GitHub run: `[external evidence reference]
75
+ - Job env in failed shard included `E2E_TARGET_ENVIRONMENT=staging`, `E2E_CHECKOUT_REF=673813bc6603911dcd8bb3dff83edbd96585c5a8`, and blank `EXPECTED_DEPLOYED_COMMIT_SHA` / `DEPLOYED_FRONTEND_BUILD_DIR`.
76
+ - Workflow code already has a deploy assertion path, but `.github/actions/playwright-tests/action.yml` only runs `node --run playwright:assert-deployed-commit` when both `E2E_TARGET_ENVIRONMENT` and `EXPECTED_DEPLOYED_COMMIT_SHA` are present.
77
+ - LLM report: one failed test, all three attempts failed with the same missing locator.
78
+ - Timeline: Cognito password login succeeded (`POST [external evidence reference] returned 200); `/api/facilityUser/findByEmail`returned 200;`/my-account` document returned 200; the click then waited 30 seconds for the missing button.
79
+ - Failure page snapshots showed authenticated My Account page content and `Personal Information` with only `button "Edit"`; `Change password` was absent in all three snapshots.
80
+ - Source at the failing commit shows `PersonalInfoSection` renders `Change password` unconditionally after removing the old `isChangePasswordEnabled` prop.
81
+ - Prior related [ticket redacted] was duplicate of [ticket redacted] and covered by [ticket redacted] for a different failure mode: My Account error state after `findByEmail` schema validation. That is not this occurrence because the page content loaded successfully.
82
+
83
+ **Proposed Fix:**
84
+
85
+ 1. In `.github/workflows/e2e-tests-staging.yml` / `.github/actions/playwright-tests/action.yml`, make source-coupled staging E2E runs fail fast when they run against a remote frontend without an asserted deployed commit. For `workflow_dispatch`, either require `expected_deployed_commit_sha` for `playwright:run:critical-e2e` or default it to the checked-out SHA when the intent is to validate current `main`. Reuse the existing `playwright:assert-deployed-commit` path rather than adding a new polling mechanism.
86
+ 2. Add a small, deterministic My Account page-ready helper or inline guard in `playwright/e2e/myAccount.spec.ts`: after `page.goto("/my-account")`, assert the heading, signed-in email, and `Personal Information` are visible before interacting with `Change password`. Then assert `Change password` is visible before clicking so a missing action fails as a contract/deploy mismatch instead of a 30s action timeout.
87
+ 3. Add diagnostics for future occurrences: when the button is absent, record the app version/deployed commit if available, the current URL, and a compact account action snapshot. Prefer wiring this through a reusable Playwright helper rather than ad hoc sleeps/timeouts.
88
+
89
+ **Observability To Reach 5/5:**
90
+
91
+ - Add deployed frontend commit/app version to the Playwright LLM report environment or per-attempt metadata. The app already exposes `__APP_VERSION__` in source and has a Debug page; make this easy for E2E diagnostics to capture.
92
+ - Include `E2E_CHECKOUT_REF`, `EXPECTED_DEPLOYED_COMMIT_SHA`, `E2E_TARGET_ENVIRONMENT`, and the canonical remote frontend version in the LLM report or a test annotation for every remote E2E run.
93
+ - If remote E2E runs intentionally allow deployed-source drift, add an explicit annotation so flaky-test triage can distinguish product/test failures from stale deployment validation failures.
94
+
95
+ **Sibling Candidates:**
96
+
97
+ - Other source-coupled critical E2E tests run by `.github/workflows/e2e-tests-staging.yml` via `workflow_dispatch` with no `expected_deployed_commit_sha`.
98
+ - Other tests that navigate directly to a feature page and immediately click an element without first asserting a deterministic page-ready contract.
99
+
100
+ **Validation Plan:**
101
+
102
+ - `npm run lint:fast -- playwright/e2e/myAccount.spec.ts .github/actions/playwright-tests/action.yml .github/workflows/e2e-tests-staging.yml` if the repo command supports path args; otherwise run `npm run lint:fast`.
103
+ - `npm run typecheck`.
104
+ - For the test change, run the smallest relevant Playwright command against an environment with a pinned deployed commit: `E2E_TARGET_ENVIRONMENT=staging EXPECTED_DEPLOYED_COMMIT_SHA=<sha> npm run playwright:run:critical-e2e -- --project="Desktop Chrome" --grep "user changes password from the Change Password dialog"`.
105
+ - Also run `node --run playwright:assert-deployed-commit` with `E2E_TARGET_ENVIRONMENT=staging` and a known current deployed SHA to verify the fast-fail path.
106
+
107
+ **Open Questions:**
108
+
109
+ - Confirm whether Flaky Test Management manually dispatches `e2e-tests-staging.yml` without `expected_deployed_commit_sha`. If yes, update that dispatch path as part of the fix.
110
+ - Decide whether manual staging runs should require the input or default to the current deployed SHA. For flaky remediation, requiring/passing the expected SHA is safer.
111
+
112
+ **Residual Risk:**
113
+
114
+ - If staging legitimately rolls a different UI contract while tests are executing, pinned deploy checks prevent false source/test failures but cannot make a mismatched deployment pass.
115
+ - After deploy drift is removed, the password mutation itself can still fail due to Cognito throttling or account setup issues; those are separate surfaces and should have their own artifacts.
116
+
117
+ ## Required change / Datadog Remediation Instructions
118
+
119
+ - Include both the investigation ticket ID (`[ticket redacted]`) and this implementation ticket ID in the change body.
120
+ - `pup api -X POST v2/test/flaky-test-management/tests`
121
+ - Implement this plan using the `core:go` skill.
122
+
123
+ ## Related Tickets
124
+
125
+ - Investigation: [ticket redacted]
126
+ - Prior related investigation: [ticket redacted]
127
+ - Prior same-area investigation found via comments: [ticket redacted]
128
+ - Prior implementation for different root cause: [ticket redacted]
@@ -0,0 +1,99 @@
1
+ # Historical plan snapshot
2
+
3
+ ## Groundcrew
4
+
5
+ Repository: `[admin frontend]`
6
+ Implementation workflow: use the `cb-work` skill. If unavailable, use the `core:go` skill.
7
+
8
+ Include this implementation ticket ID in the change body.
9
+
10
+ ```bash
11
+
12
+ ```
13
+
14
+ ## Source Investigation
15
+
16
+ Investigation ticket: [ticket redacted]
17
+ Skill/mode: `flaky-debug` in plan mode. The requested `flaky-test-debugger` skill was not installed under that exact name, so this used the available Clipboard flaky-debug skill.
18
+
19
+ ## Flake Details
20
+
21
+ ```json
22
+ {
23
+ "repo": "[admin frontend]",
24
+ "test": "Home Health Full Lifecycle complete home health workflow: create case, manage visits, verify and reject occurrences, close case",
25
+ "file": "playwright/e2e/homeHealth/fullLifecycle.spec.ts:1170",
26
+ "framework": "playwright",
27
+ "isNewFlaky": false,
28
+ "failures": [
29
+ {
30
+ "error": "TimeoutError: locator.click: Timeout 5000ms exceeded.\nCall log:\n - waiting for getByRole('dialog').filter({ hasText: 'Are you sure you want to close this case?' }).getByRole('button', { name: 'Close', exact: true })\n - locator canonical to <button tabindex=\"0\" type=\"button\" class=\"MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-fullWidth MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-fullWidth css-6qhbw3\">…</button>\n - attempting click action\n - waiting for element to be visible, enabled and stable\n - element is not stable\n - retrying click action\n - waiting for element to be visible, enabled and stable\n - element was detached from the DOM, retrying\n",
31
+ "stack": "TimeoutError: locator.click: Timeout 5000ms exceeded.\nCall log:\n - waiting for getByRole('dialog').filter({ hasText: 'Are you sure you want to close this case?' }).getByRole('button', { name: 'Close', exact: true })\n - locator canonical to <button tabindex=\"0\" type=\"button\" class=\"MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-fullWidth MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-fullWidth css-6qhbw3\">…</button>\n - attempting click action\n - waiting for element to be visible, enabled and stable\n - element is not stable\n - retrying click action\n - waiting for element to be visible, enabled and stable\n - element was detached from the DOM, retrying\n\n at submitCloseCase (/opt/actions-runner/_work/[admin frontend]/[admin frontend]/playwright/e2e/homeHealth/fullLifecycle.spec.ts:792:17)\n at /opt/actions-runner/_work/[admin frontend]/[admin frontend]/playwright/e2e/homeHealth/fullLifecycle.spec.ts:1782:9\n at /opt/actions-runner/_work/[admin frontend]/[admin frontend]/playwright/e2e/homeHealth/fullLifecycle.spec.ts:1774:7\n at WorkerMain._runTest (/opt/actions-runner/_work/[admin frontend]/[admin frontend]/node_modules/dd-trace/packages/datadog-instrumentations/src/playwright.js:1692:5)",
32
+ "branch": "main",
33
+ "pipeline": "[external evidence reference]",
34
+ "commit": "ae7ad5ee34fd9d833c47c68aec34f8d3e4c03745",
35
+ "durationMs": 56753,
36
+ "shard": "2/4",
37
+ "timestamp": "2026-07-06T17:16:19Z"
38
+ }
39
+ ]
40
+ }
41
+ ```
42
+
43
+ ## Plan Output
44
+
45
+ **Test ID:** `[bf2e9756e2c0]`
46
+
47
+ **Confidence:** 4/5. The failure artifact directly shows the close dialog button was visible/enabled, the click timed out because the element became unstable/detached, and no close-case PATCH was emitted. The exact reason the dialog disappeared is inferred from UI/test timing, so this is not 5/5.
48
+
49
+ **Failure surface:** User action no-op / test harness. The app reached the confirmation dialog, but the destructive close-case request never started.
50
+
51
+ **Current main status:** The failing code path still exists on current main/worktree in `playwright/e2e/homeHealth/fullLifecycle.spec.ts`: `submitCloseCase` waits for `PATCH /cases/{caseId}` and clicks the `Close` button in a single `Promise.all` at lines 784-792. No open change with label `flaky-test-fix` matched this file. Recent commit `28bdf114626` / [ticket redacted] fixed a different close-case flake where the PATCH succeeded and the helper retried a one-shot mutation; this new failure happens before the PATCH starts.
52
+
53
+ **Symptom:** In Step j, `submitCloseCase` times out on `closeButton.click({ timeout: ATTEMPT_TIMEOUT_MS })`. Playwright reports the button canonical, then was unstable and detached. The paired `waitForHomeHealthResponse` also times out because no matching `PATCH /[home-health service]/api/v1/*/cases/{caseId}` occurs.
54
+
55
+ **Root cause:** `submitCloseCase` treats one transient close-dialog/button remount as terminal. The close dialog is mounted under `CaseActionsMenu` / `CaseClosureDialog`; the failed run shows it can disappear or remount during the click window before the close PATCH starts. The helper lacks the request-observed guard used elsewhere in this spec for visit verification actions, so a pre-request click detach fails the test instead of safely reopening the dialog and trying again.
56
+
57
+ **Evidence:**
58
+
59
+ - LLM report summary: 52 tests total, 49 passed, 1 flaky, 2 skipped. This test failed first attempt and passed retry.
60
+ - Failed timeline around Step j: `Case Actions` clicked at ~49071ms, `Close Case` clicked at ~49113ms, dialog text visible at ~49374ms, `Close` button visible/enabled at ~49391-49394ms, then both `waitForResponse` and `Click ... Close` start at ~49398ms and time out after 5000ms.
61
+ - Failed network activity after the close click contains Datadog/Braze and unrelated `/api/extra-worked-time-requests` and `/api/user/get` requests, but no `PATCH /[home-health service]/api/v1/.../cases/{caseId}`.
62
+ - Failure screenshot shows the dialog is gone, the case is still visible under Active Cases, and the case has not moved to ended Cases.
63
+ - Passed retry follows the same Step j path and completes: click Case Actions, click Close Case, see close dialog, click Close, settle Active Cases, then verify the case under ended Cases.
64
+ - Product code path: `src/appV2/ExperimentalHomeHealth/Cases/CaseDashboard/CaseClosureDialog.tsx` only calls `updateCase` from the `Close` button handler, then closes the modal after success; `useUpdateCase` invalidates open/ended case queries on success.
65
+
66
+ **Proposed fix:** Test harness only, in `playwright/e2e/homeHealth/fullLifecycle.spec.ts`.
67
+
68
+ 1. Refactor `submitCloseCase` to mirror the existing request-observed pattern used by `clickVisitOccurrenceActionButtonUntilPatchObserved`.
69
+ 2. Start a close-case response result promise with `waitForHomeHealthResponseResult({ action: "close case", isMatchingRequest: (request) => isCaseUpdateRequest(request, caseId), timeout: ACTION_TIMEOUT_MS })` before retrying pre-request UI work.
70
+ 3. Add a helper such as `clickCloseCaseButtonUntilPatchObserved({ page, closureDialog, caseId })` that:
71
+ - creates `page.waitForRequest((request) => isCaseUpdateRequest(request, caseId), { timeout: ATTEMPT_TIMEOUT_MS }).then(() => true, () => false)`;
72
+ - locates the Close button fresh from the current dialog;
73
+ - asserts visible/enabled and clicks;
74
+ - if the click throws but the PATCH was observed, returns success;
75
+ - if no PATCH was observed, throws a retryable pre-request error.
76
+ 4. Wrap only the pre-mutation open/fill/click path in `expect(...).toPass({ intervals: [500, 1_000, 2_000], timeout: ACTION_TIMEOUT_MS })` so retries are allowed before a close PATCH starts, but the mutation itself is still executed once.
77
+ 5. After the helper observes the PATCH, await the response result with `getSuccessfulHomeHealthResponse`, assert `data.attributes.status === CaseStatus.[attempt reference redacted]`, settle the dashboard, and assert the active case card disappears.
78
+ 6. Add close-case diagnostics on final failure, similar to `attachCancelVisitDialogOpenDiagnostics`: visible dialog texts, close button count/visible/enabled, case card text, current URL, whether the close PATCH was observed, and a screenshot attachment if useful.
79
+ 7. Do not use `force: true`, fixed sleeps, or a longer click timeout as the fix.
80
+
81
+ **Observability to reach 5/5:** Add the close-case test diagnostics above. They would confirm whether the dialog ended from an `onClose`, remounted with a new button, or disappeared because the case card subtree remounted. No backend telemetry is required for this occurrence because the expected backend request never started.
82
+
83
+ **Sibling candidates:** The exact `Promise.all([waitForHomeHealthResponse, closeButton.click])` anti-pattern is localized to close case. Review nearby destructive dialog submits in the same file while implementing: cancel visit confirmations and remove booked worker use a request promise plus direct click; they may not need changes, but should be checked against the same request-observed guard criterion.
84
+
85
+ **Validation plan:**
86
+
87
+ ```bash
88
+ npx oxfmt --check playwright/e2e/homeHealth/fullLifecycle.spec.ts
89
+ npx oxlint playwright/e2e/homeHealth/fullLifecycle.spec.ts
90
+ npm run lint:fast
91
+ npm run typecheck
92
+ npm run playwright:run -- playwright/e2e/homeHealth/fullLifecycle.spec.ts --grep "complete home health workflow"
93
+ ```
94
+
95
+ If local Playwright setup requires deployed-env credentials, record the setup blocker and run the smallest available lint/typecheck verification locally.
96
+
97
+ **Open questions:** None for implementation. The fix should not require product-code changes unless new diagnostics show real user-visible close-dialog dismissal without a request.
98
+
99
+ **Residual risk:** The full lifecycle spec has several independently flaky surfaces. This plan only addresses the close-case confirm-button detach before the PATCH starts; later ended-tab verification or unrelated home-health API/setup flakes can still fail separately.