@clipboard-health/ai-rules 2.38.1 โ 2.38.3
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.
- package/package.json +1 -1
- package/skills/cb-babysit/SKILL.md +4 -4
- package/skills/cb-babysit/scripts/_sentinel.sh +1 -1
- package/skills/cb-ship/references/pr-template.md +1 -1
- package/skills/flaky-critic/references/rubric.md +3 -1
- package/skills/flaky-debug/SKILL.md +44 -5
- package/skills/flaky-debug/references/datadog-apm-traces.md +135 -54
- package/skills/flaky-debug/references/plan-e2e.md +2 -0
- package/skills/flaky-debug/references/plan.md +1 -0
- package/skills/flaky-debug/references/root-cause-kb/README.md +44 -0
- package/skills/flaky-debug/references/root-cause-kb/cdc-read-model-readiness.md +51 -0
- package/skills/flaky-debug/references/root-cause-kb/cdn-503-amplification.md +50 -0
- package/skills/flaky-debug/references/root-cause-kb/cognito-api-throttling.md +51 -0
- package/skills/flaky-debug/references/root-cause-kb/cognito-email-alias-eventual-consistency.md +50 -0
- package/skills/flaky-debug/references/root-cause-kb/feature-flag-and-third-party-bootstrap.md +55 -0
- package/skills/flaky-debug/references/root-cause-kb/lazy-chunk-load-recovery.md +51 -0
- package/skills/flaky-debug/references/root-cause-kb/query-driven-list-dialog-teardown.md +52 -0
- package/skills/flaky-debug/references/root-cause-kb/user-event-and-fake-timer-timing.md +52 -0
- package/skills/flaky-triage/SKILL.md +3 -1
package/package.json
CHANGED
|
@@ -19,9 +19,9 @@ Resolve bundled "./scripts" paths relative to SKILL.md.
|
|
|
19
19
|
|
|
20
20
|
The skill uses two sentinels with visible footer lines.
|
|
21
21
|
|
|
22
|
-
**Addressed sentinel**: `<sub>๐ค <code>cb-babysit:addressed v1 core@3.17.
|
|
22
|
+
**Addressed sentinel**: `<sub>๐ค <code>cb-babysit:addressed v1 core@3.17.2</code></sub>`. Appended on its own line at the end of every reply the skill posts so re-runs know which threads and review-body comments are already handled. Dedupe also recognizes the legacy `babysit-pr:addressed v1` prefix from before this skill was renamed.
|
|
23
23
|
|
|
24
|
-
**Follow-up sentinel**: `<sub>๐ค <code>cb-babysit:followup v1 core@3.17.
|
|
24
|
+
**Follow-up sentinel**: `<sub>๐ค <code>cb-babysit:followup v1 core@3.17.2</code></sub>`. Attached to replies that defer an out-of-scope comment as a tracked follow-up. The sentinel is additive: the post-reply scripts still append the `addressed` sentinel at the end.
|
|
25
25
|
|
|
26
26
|
**Sentinel recency rules.** The script emits a per-thread `activityState` with three values. Step 6a owns the handling rules for each state.
|
|
27
27
|
|
|
@@ -247,7 +247,7 @@ Body templates (the script appends the `addressed` sentinel if missing):
|
|
|
247
247
|
- **Agree**: `Addressed in <commit-url>. <one-line what-changed>.`
|
|
248
248
|
- **Disagree**: `Leaving current behavior. <reasoning>.`
|
|
249
249
|
- **Already fixed**: `Already handled by <commit-url-or-file:line>. <brief pointer>.`
|
|
250
|
-
- **Defer**: `Out of scope for this PR; this looks like follow-up work rather than something introduced or required by this change. <one-line rationale or pointer if useful>.\n\n<sub>๐ค <code>cb-babysit:followup v1 core@3.17.
|
|
250
|
+
- **Defer**: `Out of scope for this PR; this looks like follow-up work rather than something introduced or required by this change. <one-line rationale or pointer if useful>.\n\n<sub>๐ค <code>cb-babysit:followup v1 core@3.17.2</code></sub>`
|
|
251
251
|
|
|
252
252
|
For Defer replies, include the follow-up sentinel on its own line as shown. The script will append the `addressed` sentinel after it on its own line, so the final body ends with the follow-up sentinel followed by a blank line followed by the `addressed` sentinel. `grep cb-babysit:followup` finds the deferral and `grep cb-babysit:addressed` still marks the thread handled for dedupe.
|
|
253
253
|
|
|
@@ -263,7 +263,7 @@ The PR-level summary should:
|
|
|
263
263
|
|
|
264
264
|
- Group by source. Use `## Review-body findings` for step-7 work and `## Conversation-tab comments` for step-6b work. Omit a section if its list is empty.
|
|
265
265
|
- Inside each section, group verdicts under **Agree / Disagree / Already fixed / Deferred (out of scope)** subheadings. Omit a subheading if its list is empty.
|
|
266
|
-
- Under **Deferred (out of scope)**, list each deferred item as a bullet, followed on its own line by `<sub>๐ค <code>cb-babysit:followup v1 core@3.17.
|
|
266
|
+
- Under **Deferred (out of scope)**, list each deferred item as a bullet, followed on its own line by `<sub>๐ค <code>cb-babysit:followup v1 core@3.17.2</code></sub>` so grep catches them individually.
|
|
267
267
|
- Include the commit URL for fixes.
|
|
268
268
|
- End with a fenced fingerprint block listing every current fingerprint (addressed and deferred) one per line. Include both `reviewBodyComments[].fingerprint` (whole-body, one per automated review) and `activeIssueComments[].fingerprint` (per Conversation-tab comment). Future runs dedupe by matching these against `priorBabysitSentinels`.
|
|
269
269
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
SENTINEL_PREFIX='cb-babysit:addressed v1 '
|
|
12
12
|
LEGACY_SENTINEL_PREFIX='babysit-pr:addressed v1 '
|
|
13
|
-
SENTINEL='<sub>๐ค <code>cb-babysit:addressed v1 core@3.17.
|
|
13
|
+
SENTINEL='<sub>๐ค <code>cb-babysit:addressed v1 core@3.17.2</code></sub>'
|
|
14
14
|
|
|
15
15
|
# Bot author allowlist (JSON array literal). Used by unresolvedPrComments.sh
|
|
16
16
|
# as a fallback when GraphQL's `author.__typename == "Bot"` misses a GitHub
|
|
@@ -17,5 +17,5 @@ Concisely explain the user intent from session history and the meaningful behavi
|
|
|
17
17
|
|
|
18
18
|
Optional, don't fabricate: ticket links, rollout plan, residual risk, or specific areas for reviewers to focus.
|
|
19
19
|
|
|
20
|
-
<sub>๐ค <code>cb-ship:created v1 core@3.17.
|
|
20
|
+
<sub>๐ค <code>cb-ship:created v1 core@3.17.2</code></sub>
|
|
21
21
|
```
|
|
@@ -8,7 +8,7 @@ This rubric turns Rocky's observed review standards into checkable rules for fla
|
|
|
8
8
|
|
|
9
9
|
Findings from automated reviewers (Mendral, CodeRabbit, etc.) are **advisory inputs, not binding rules**: the critic evaluates each on rubric merits and may decline one with stated reasoning, exactly as cb-babysit's Disagree replies do.
|
|
10
10
|
|
|
11
|
-
**Statement-missing vs substance-missing (backtest amendment, 2026-06-11):** B5/B6/B7/B8 each require an explicit statement in the plan (dedup-check results, confidence score plus observability section, current-main status, causal-chain section). When the _statement_ is absent but the violation is not demonstrated โ no actual duplicate found, no actual low-confidence code fix, no actual staleness, no demonstrably symptom-terminated diagnosis โ the verdict is reject with disposition **amend-and-resubmit**: one bounce asking for exactly the missing sections, never escalation. A demonstrated violation (a real duplicate, a real โค2/5 code fix, a diagnosis that demonstrably stops at a symptom) is a substantive reject. The 2026-06-11 backtest showed 6 of 7 false rejections were statement-missing on plans predating these conventions; live plans carry the sections by template, but the distinction stays load-bearing for the agreement metric.
|
|
11
|
+
**Statement-missing vs substance-missing (backtest amendment, 2026-06-11):** B5/B6/B7/B8 each require an explicit statement in the plan (dedup-check results, Prior attempts section when applicable, confidence score plus observability section, current-main status, causal-chain section). When the _statement_ is absent but the violation is not demonstrated โ no actual duplicate found, no actual prior implementation tickets for the fingerprint family, no actual low-confidence code fix, no actual staleness, no demonstrably symptom-terminated diagnosis โ the verdict is reject with disposition **amend-and-resubmit**: one bounce asking for exactly the missing sections, never escalation. A demonstrated violation (a real duplicate, a mechanism already tried by a prior merged fix, a real โค2/5 code fix, a diagnosis that demonstrably stops at a symptom) is a substantive reject. The 2026-06-11 backtest showed 6 of 7 false rejections were statement-missing on plans predating these conventions; live plans carry the sections by template, but the distinction stays load-bearing for the agreement metric.
|
|
12
12
|
|
|
13
13
|
## 2. Banned patterns
|
|
14
14
|
|
|
@@ -54,6 +54,8 @@ Opening one plan/PR per failing test when the failures share one mechanism (same
|
|
|
54
54
|
|
|
55
55
|
**Prior-sibling signal (backtest amendment, 2026-06-11):** sibling PRs for the same mechanism that were closed _unmerged_ are a strong reject signal, not mere context. A plan citing closed-unmerged siblings must explain what is materially different this time โ narrower scope alone is not an answer (the backtest's one genuine false approval cited prior wontfix siblings and explained them away).
|
|
56
56
|
|
|
57
|
+
**Prior attempts (amendment, 2026-07-16):** when a fingerprint family has prior implementation tickets, the plan MUST contain a **Prior attempts** section listing each prior ticket/PR, what it blamed, what it changed, and the recurrence evidence showing its diagnosis was wrong or incomplete. A missing section is statement-missing and gets amend-and-resubmit per ยง1. A plan that re-proposes a mechanism a prior merged fix already tried is a substantive reject. A family with **โฅ2 failed merged fixes** โ merged fixes followed by recurrence โ is disqualified from the normal fix path: verdict = reject and escalate as needs-human. Apply the `chronic` label and hold the ticket for the deep-dive track (a dedicated dossier-first investigation stage; until it exists, chronic tickets stay held with the label for human routing). The โฅ2 threshold is intentionally different from flaky-triage's โฅ3-prior-implementation-tickets screen: triage counts tickets cheaply before any diagnosis exists, while the critic can see merge and recurrence outcomes and applies the sharper failed-merged-fix test. Either stage flagging chronic routes the same way; the counts are staged by the evidence available at each stage, not a contradiction.
|
|
58
|
+
|
|
57
59
|
### B6 โ Code fix on low-confidence diagnosis
|
|
58
60
|
|
|
59
61
|
Proposing a code change when root-cause confidence is โค2/5, or any plan below 5/5 that omits observability follow-ups.
|
|
@@ -3,7 +3,32 @@ name: flaky-debug
|
|
|
3
3
|
description: Debug and fix flaky tests including Playwright E2E, NestJS service/integration, React component, and unit tests.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Phases run in order. Skip a phase if you already have the information it produces. Phase 3 runs only in fix mode.
|
|
6
|
+
Phases run in order. Phase 0 is mandatory. Skip a later phase if you already have the information it produces. Phase 3 runs only in fix mode.
|
|
7
|
+
|
|
8
|
+
## Phase 0: Verify investigation access
|
|
9
|
+
|
|
10
|
+
Run these checks before classifying or diagnosing the failure. Do not silently continue with reduced evidence if a check fails.
|
|
11
|
+
|
|
12
|
+
1. Verify that `pup` can read APM spans. Do not use `pup auth status`; it can fail in sandboxes where environment-variable authentication works.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
command -v pup
|
|
16
|
+
pup traces search \
|
|
17
|
+
--query="service:cbh-backend-main env:staging" \
|
|
18
|
+
--from=15m \
|
|
19
|
+
--limit=1 \
|
|
20
|
+
--jq='.data | length'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
A successful query is sufficient even when the count is zero. If `pup` is missing, install it and rerun the check. For `401`/authentication failures, run `pup auth login` on a developer machine or provide `DD_API_KEY` and `DD_APP_KEY` in a sandbox/CI session. For `403`, obtain an application key or OAuth session with APM read access.
|
|
24
|
+
|
|
25
|
+
2. For a CI-sourced E2E failure, verify artifact access by fetching the exact run before doing any diagnosis:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bash "<flaky-debug-skill-dir>/scripts/fetch-llm-report.sh" "<github-actions-url>"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If GitHub authentication fails, run `gh auth login --hostname github.com` and ensure the token can read Actions artifacts in the repository. If the artifact is missing or expired, rerun the source workflow or obtain the `playwright-llm-report`/Playwright HTML report from the user. A missing run URL or inaccessible artifact blocks a CI-sourced E2E investigation; report the failed command and remediation instead of degrading the confidence score. Artifact access is not applicable to service, component, or unit failures that were reproduced locally with complete output.
|
|
7
32
|
|
|
8
33
|
## Mode: plan vs fix
|
|
9
34
|
|
|
@@ -33,15 +58,28 @@ If the type is ambiguous, check the test file extension and imports to confirm.
|
|
|
33
58
|
|
|
34
59
|
If the type is E2E, also classify where the failure surfaced in the lifecycle -- see [Classify the E2E Failure Surface](./references/plan-e2e.md#classify-the-e2e-failure-surface) in `plan-e2e.md`. The failure surface dictates how broadly to investigate before reading or editing the test.
|
|
35
60
|
|
|
36
|
-
## Phase 1b: Check for Existing Fixes
|
|
61
|
+
## Phase 1b: Build the Dossier and Check for Existing Fixes
|
|
62
|
+
|
|
63
|
+
Before diagnosing, build the fingerprint family's dossier, then check whether someone (or another agent) has already fixed this flake. Do not limit the dossier to the failing file or recent activity.
|
|
64
|
+
|
|
65
|
+
1. **Search Linear for the fingerprint family and exact test title.** Derive the fingerprint family from the ticket title or Flake details and the full test title from `testName`. Run these four `list_issues` queries, then union and deduplicate the results by ticket ID:
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
project: "Groundcrew", label: "flaky-investigation", query: "<fingerprint-family>"
|
|
69
|
+
project: "Groundcrew", label: "flaky-investigation", query: "<exact full test title>"
|
|
70
|
+
project: "Groundcrew", label: "flaky-implementation", query: "<fingerprint-family>"
|
|
71
|
+
project: "Groundcrew", label: "flaky-implementation", query: "<exact full test title>"
|
|
72
|
+
```
|
|
37
73
|
|
|
38
|
-
|
|
74
|
+
For all four queries, omit the state filter so every state, including terminal states, is searched. Omit created/updated date filters; there is no 14-day restriction. The `query` field must search issue titles and descriptions, not the file name alone.
|
|
75
|
+
- Fetch every matching issue with `get_issue`, then read its relations, comments, and linked PRs so closed and merged attempts are not lost to truncated list results
|
|
39
76
|
|
|
40
|
-
|
|
77
|
+
2. **Build the `Prior attempts` table before diagnosis.** Add one row for each prior implementation ticket and linked PR found through either query. The table columns are `Prior ticket/PR`, `What it blamed`, `What it changed`, and `Recurrence evidence`. The table must list each prior ticket/PR, what it blamed, what it changed, and the recurrence evidence showing its diagnosis was wrong or incomplete. Use later investigation sightings and post-merge failures as recurrence evidence; do not infer failure merely because an attempt is old. If there are no prior implementation tickets, record `None found` plus the fingerprint and test-title searches run.
|
|
78
|
+
3. **Search open PRs with the `flaky-test-fix` label** that touch the failing test file or its surrounding code. Use GitHub search scoped to the repo:
|
|
41
79
|
- Search PRs labeled `flaky-test-fix` for the test file name or test directory
|
|
42
80
|
- Review the PR's changes to assess whether they address the same flake pattern with reasonable confidence โ if so, stop and report it to the user rather than opening a duplicate fix
|
|
43
81
|
- If the PR only partially addresses the flake or targets a different root cause, note it and proceed with investigation
|
|
44
|
-
|
|
82
|
+
4. **Check recent commits on `main`** that touch the failing test file or its surrounding code:
|
|
45
83
|
- `git log --oneline -20 origin/main -- <test-file-path>` and also check the parent directory or related source files
|
|
46
84
|
- Read the commit messages โ if one clearly fixes the same flake pattern, stop and report it to the user
|
|
47
85
|
|
|
@@ -50,6 +88,7 @@ If an existing fix is found, report:
|
|
|
50
88
|
- The PR number/URL or commit hash
|
|
51
89
|
- A brief summary of what it addresses
|
|
52
90
|
- Whether it fully covers the current flake or only partially
|
|
91
|
+
- The `Prior attempts` table, including recurrence evidence for any failed prior diagnosis
|
|
53
92
|
|
|
54
93
|
If no existing fix is found, proceed to Phase 2.
|
|
55
94
|
|
|
@@ -1,84 +1,165 @@
|
|
|
1
1
|
# Datadog APM Traces
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Correlate a Playwright network instance with backend spans in Datadog's **APM Spans index**. Use the per-request `attempts[].network.instances[].traceId`; do not use the test-level fixture annotation.
|
|
4
4
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Phase 0 of the flaky-debug skill must already have proved that `pup` can read APM spans and that the exact CI artifact is accessible. Do not run `pup auth status`; call `pup traces search` directly because environment-variable authentication can work in sandboxes where `pup auth status` does not.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- **Environment variables** (`DD_API_KEY` + `DD_APP_KEY`) โ the path used in sandboxes and CI.
|
|
9
|
+
If a query fails with `401`, run `pup auth login` or provide `DD_API_KEY` and `DD_APP_KEY`. For `403`, obtain APM read access. Do not continue an investigation without this access.
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
## Which trace ID is authoritative?
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
Use these sources in order:
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- **`--limit` defaults to 50.** Max is 1000. For large traces, you may need multiple paginated calls (but pup handles most pagination internally).
|
|
20
|
-
- **Query syntax for traces:** `service:<name> resource_name:<path> @duration:>5s env:production status:error operation_name:<op>`
|
|
15
|
+
1. The response `traceparent`, exposed as `network.instances[].traceId`. It identifies the context the backend actually selected.
|
|
16
|
+
2. For an aborted or unreadable response, the request's `x-datadog-trace-id`. Reporter versions containing the Datadog fallback normalize its unsigned decimal value to a 32-character, zero-padded hexadecimal trace ID.
|
|
17
|
+
3. The request `traceparent` only when no Datadog request context exists.
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
Do **not** query the test-level `traceparent` annotation. The Playwright fixture sets a W3C header, but Datadog RUM also adds a different `x-datadog-*` context to first-party requests. The observed staging backend selects the Datadog context when the two conflict.
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
For reports produced by an older reporter, inspect the full Playwright trace's request headers. Convert `x-datadog-trace-id` as described below. If neither a response trace nor the Datadog request header is available, use the request-ID correlation path instead of claiming that the fixture ID represents the backend trace.
|
|
22
|
+
|
|
23
|
+
## Known-good query recipe
|
|
24
|
+
|
|
25
|
+
### 1. Set an exact time window
|
|
26
|
+
|
|
27
|
+
Derive the request time from the attempt's `startTime` plus the network instance's `offsetMs`. Use a narrow absolute RFC 3339 window around the request; this avoids false matches and is safer than relying on `pup`'s one-hour default.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pup traces search \
|
|
31
|
+
--query='env:staging trace_id:<TRACE_ID>' \
|
|
32
|
+
--from='2026-07-06T14:10:00Z' \
|
|
33
|
+
--to='2026-07-06T14:20:00Z' \
|
|
34
|
+
--limit=1000
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Start without a service filter when ownership is uncertain. Once the service is known, add it to reduce noise.
|
|
38
|
+
|
|
39
|
+
### 2. Use the staging service name
|
|
40
|
+
|
|
41
|
+
Staging APM service names are `cbh-*` prefixed. For example:
|
|
25
42
|
|
|
26
43
|
```bash
|
|
27
|
-
pup traces search
|
|
44
|
+
pup traces search \
|
|
45
|
+
--query='env:staging service:cbh-backend-main trace_id:<TRACE_ID>' \
|
|
46
|
+
--from='<RFC3339_START>' \
|
|
47
|
+
--to='<RFC3339_END>' \
|
|
48
|
+
--limit=1000
|
|
28
49
|
```
|
|
29
50
|
|
|
30
|
-
|
|
51
|
+
`service:cbh-backend-main` works; `service:backend-main` returns nothing. Other examples include `cbh-user`, `cbh-bg-jobs`, and `cbh-documents-service-backend`.
|
|
31
52
|
|
|
32
|
-
|
|
53
|
+
### 3. Use a supported ID representation
|
|
33
54
|
|
|
34
|
-
|
|
55
|
+
`pup traces search` accepts either:
|
|
35
56
|
|
|
36
|
-
|
|
57
|
+
- the full 32-character hexadecimal trace ID returned by the spans API, or
|
|
58
|
+
- the unsigned decimal value of the lower 64 bits.
|
|
59
|
+
|
|
60
|
+
Do not convert the entire 128-bit value to decimal; the APM Spans search did not match that representation in the live verification.
|
|
61
|
+
|
|
62
|
+
Convert a 32-character W3C/Datadog hexadecimal ID to its lower-64 decimal form:
|
|
37
63
|
|
|
38
64
|
```bash
|
|
39
|
-
|
|
65
|
+
node -e '
|
|
66
|
+
const id = process.argv[1].replace(/^0x/, "");
|
|
67
|
+
if (!/^[0-9a-f]{32}$/i.test(id)) process.exit(1);
|
|
68
|
+
console.log(BigInt(`0x${id.slice(-16)}`).toString(10));
|
|
69
|
+
' '<TRACE_ID>'
|
|
40
70
|
```
|
|
41
71
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
.span_id โ unique span identifier
|
|
51
|
-
.trace_id โ shared across all spans in the trace
|
|
52
|
-
.parent_id โ parent span (for building the call tree)
|
|
53
|
-
.service โ service name (e.g., "cbh-backend-main")
|
|
54
|
-
.operation_name โ operation (e.g., "express.request", "express.middleware", "http.request")
|
|
55
|
-
.resource_name โ resource (e.g., "GET /api/v1/users", "<anonymous>")
|
|
56
|
-
.status โ "ok" or "error"
|
|
57
|
-
.start_timestamp โ ISO 8601 start time
|
|
58
|
-
.end_timestamp โ ISO 8601 end time
|
|
59
|
-
.custom.duration โ duration in NANOSECONDS (divide by 1,000,000 for ms)
|
|
60
|
-
.custom.env โ environment (e.g., "staging", "production")
|
|
61
|
-
.custom.error โ error object with .message, .file, .fingerprint (null if no error)
|
|
62
|
-
.custom.type โ span type (e.g., "web", "http", "mongodb", "redis", "worker")
|
|
63
|
-
.custom.service โ service name (also at top level)
|
|
64
|
-
.tags[] โ array of tag strings
|
|
72
|
+
Normalize an `x-datadog-trace-id` decimal value to the reporter's 32-character hexadecimal form:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
node -e '
|
|
76
|
+
const value = BigInt(process.argv[1]);
|
|
77
|
+
if (value <= 0n || value > 0xffffffffffffffffn) process.exit(1);
|
|
78
|
+
console.log(value.toString(16).padStart(32, "0"));
|
|
79
|
+
' '<X_DATADOG_TRACE_ID>'
|
|
65
80
|
```
|
|
66
81
|
|
|
67
|
-
|
|
82
|
+
### 4. Query a span ID only as a secondary lookup
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pup traces search \
|
|
86
|
+
--query='env:staging span_id:<SPAN_ID>' \
|
|
87
|
+
--from='<RFC3339_START>' \
|
|
88
|
+
--to='<RFC3339_END>' \
|
|
89
|
+
--limit=1
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
A response `traceparent` contains the backend span ID. A request fallback contains the upstream parent span ID, which may not itself be an indexed backend span; the trace ID remains authoritative.
|
|
93
|
+
|
|
94
|
+
### 5. Summarize relevant spans
|
|
95
|
+
|
|
96
|
+
Durations are nanoseconds. Divide by 1,000,000 for milliseconds.
|
|
68
97
|
|
|
69
98
|
```bash
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
99
|
+
pup traces search \
|
|
100
|
+
--query='env:staging trace_id:<TRACE_ID>' \
|
|
101
|
+
--from='<RFC3339_START>' \
|
|
102
|
+
--to='<RFC3339_END>' \
|
|
103
|
+
--limit=1000 \
|
|
104
|
+
| jq '[.data[].attributes | {
|
|
105
|
+
trace_id,
|
|
106
|
+
span_id,
|
|
107
|
+
parent_id,
|
|
108
|
+
service,
|
|
109
|
+
operation: .operation_name,
|
|
110
|
+
resource: .resource_name,
|
|
111
|
+
status,
|
|
112
|
+
retained_by,
|
|
113
|
+
duration_ms: ((.custom.duration // 0) / 1000000 | . * 100 | round / 100),
|
|
114
|
+
error: .custom.error.message
|
|
79
115
|
}]'
|
|
80
116
|
```
|
|
81
117
|
|
|
82
|
-
|
|
118
|
+
## Retention limitation and fallback correlation
|
|
119
|
+
|
|
120
|
+
`pup traces search` searches indexed spans, not every ingested span. An ingested trace is available in Live Search for only 15 minutes unless a retention mechanism indexes it. A zero-result indexed query after that window does **not** prove that propagation or ingestion failed.
|
|
121
|
+
|
|
122
|
+
If no indexed span exists:
|
|
123
|
+
|
|
124
|
+
1. Record the exact query, environment, service variants, ID representations, and absolute time window tried.
|
|
125
|
+
2. Search logs in the same window using the API Gateway/request ID, exact method/path, and service. The reporter may expose `requestId`; setup diagnostics may expose `apiGatewayRequestId`.
|
|
126
|
+
3. Correlate the response status/body and the owning service from the report. Do not lower confidence merely because a success trace was not selected for retention; lower it only when a necessary causal link remains unproved.
|
|
127
|
+
4. For a new occurrence, run the APM lookup within 15 minutes or add an explicit retention strategy for test traffic before relying on historical APM lookup.
|
|
128
|
+
|
|
129
|
+
Example log search shape:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pup logs search \
|
|
133
|
+
--query='env:staging "<API_GATEWAY_REQUEST_ID>"' \
|
|
134
|
+
--from='<RFC3339_START>' \
|
|
135
|
+
--to='<RFC3339_END>' \
|
|
136
|
+
--limit=100
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Audit proof: why the fixture ID cannot be the bridge
|
|
140
|
+
|
|
141
|
+
The STAFF-1792 audit used the failed attempt from `cbh-admin-frontend` CI run `29337068333` (`2026-07-14T13:35:30Z`) and its raw Playwright trace:
|
|
142
|
+
|
|
143
|
+
- The request carried fixture W3C context `00-0df9d80ac12e173dfcfc55aa29379ba6-5819a315ecd12173-01`.
|
|
144
|
+
- Datadog RUM added `x-datadog-trace-id: 13639781014258445719`, `x-datadog-parent-id: 7268017665183408395`, origin `rum`, and sampling priority `1`.
|
|
145
|
+
- The backend response carried `00-0000000000000000bd4a3c90d8fded97-277d492d139029e1-01`. `bd4a3c90d8fded97` is exactly the hexadecimal form of the request's decimal `x-datadog-trace-id`, not the fixture trace ID.
|
|
146
|
+
|
|
147
|
+
This proves header propagation reached the backend, but the backend selected the Datadog RUM context. Minting a differently shaped W3C fixture ID cannot fix that conflict by itself. The reporter therefore uses the response trace and, when the response is unreadable, the Datadog request context.
|
|
148
|
+
|
|
149
|
+
The same audit checked backend and Datadog configuration:
|
|
150
|
+
|
|
151
|
+
- `clipboard-health/src/tracer.ts` calls `tracer.init({ logLevel: "error" })` without a propagation-style override; the locked `dd-trace` version is `5.27.1`.
|
|
152
|
+
- No customer APM head-sampling rules were returned for `cbh-backend-main`/staging, and adaptive sampling was not onboarded.
|
|
153
|
+
- The retention-filter API returned `401` for the available OAuth session, so the audit could not enumerate organization retention filters and does not infer that none exist. Indexed sample spans reported `retained_by: diversity_sampling`.
|
|
154
|
+
|
|
155
|
+
Historical indexed proof comes from STAFF-1678. Its backend response trace ID `00000000000000001732fff1044f982e` converts to decimal `1671679822332401710`; this exact query returned retained `cbh-user` spans for `POST /api/user/create`:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
pup traces search \
|
|
159
|
+
--query='trace_id:1671679822332401710 env:staging service:cbh-user resource_name:"POST /api/user/create"' \
|
|
160
|
+
--from='2026-07-06T14:10:00Z' \
|
|
161
|
+
--to='2026-07-06T14:20:00Z' \
|
|
162
|
+
--limit=5
|
|
163
|
+
```
|
|
83
164
|
|
|
84
|
-
|
|
165
|
+
Those spans reported `retained_by: diversity_sampling`. This is the working alternative correlation path: use the backend-selected per-request trace ID when it was retained; otherwise correlate by API Gateway/request ID plus exact time, method, path, and service.
|
|
@@ -136,6 +136,8 @@ Scan `attempts[].network.instances[]` for 4xx/5xx responses near the failure's `
|
|
|
136
136
|
|
|
137
137
|
**`traceId`** โ when present on a failing instance (`attempts[].network.instances[].traceId`), you must follow [`./datadog-apm-traces.md`](./datadog-apm-traces.md) to correlate with backend behavior. This is the bridge between frontend test failure and potential backend root cause.
|
|
138
138
|
|
|
139
|
+
Use the per-request `network.instances[].traceId`, not the test-level `traceparent` annotation. Datadog RUM adds `x-datadog-*` headers to first-party requests, and the staging backend selects that context when it conflicts with the fixture's W3C `traceparent`. The reporter prefers the response `traceparent`; for an unreadable/aborted response it normalizes the request's `x-datadog-trace-id` to the same zero-padded hexadecimal form. Older reporter versions may fall back to the fixture ID instead; inspect the full Playwright trace for `x-datadog-trace-id` or use the request-ID/time-window correlation path in `datadog-apm-traces.md`.
|
|
140
|
+
|
|
139
141
|
If no expected request was emitted, say that explicitly and do not diagnose backend latency for that action. Instead, use the trace, console/page errors, session/permission state, and frontend code path to explain why the request never started.
|
|
140
142
|
|
|
141
143
|
Check `attempts[].network.summary` for saturation. Non-zero `instancesDroppedByGroupCap`, `instancesDroppedByInstanceCap`, or `instancesEvictedAfterAdmission` means retained content is a sample and the request you care about may have been dropped โ note this as a confidence-reducing factor. `instancesDroppedByFilter` alone is expected (static assets are filtered by design). v3 caps: instances 500, groups 200, bodies 100 per attempt.
|
|
@@ -94,6 +94,7 @@ Produce the plan with these fields:
|
|
|
94
94
|
- **Confidence:** score (1-5) with brief justification
|
|
95
95
|
- **Failure surface:** CI/job setup, test setup/auth/data, app bootstrap, user action no-op, backend request, post-success render, assertion/locator, or mixed
|
|
96
96
|
- **Current main status:** whether the failing commit's code path still exists on current `main`, has already been fixed, or has changed enough that the plan must be adjusted
|
|
97
|
+
- **Prior attempts:** list each prior ticket/PR, what it blamed, what it changed, and the recurrence evidence showing its diagnosis was wrong or incomplete. Use a table with `Prior ticket/PR`, `What it blamed`, `What it changed`, and `Recurrence evidence` columns. If the dossier search found no prior implementation tickets, write `None found` and include the fingerprint-family and exact-test-title searches run.
|
|
97
98
|
- **Symptom:** what failed and where
|
|
98
99
|
- **Root cause:** concise technical explanation
|
|
99
100
|
- **Causal chain:** each link from failing assertion to terminal cause with its evidence, or the explicit break point and the observability that would extend it (see [Causal Chain](#causal-chain))
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Flaky Root-Cause Knowledge Base
|
|
2
|
+
|
|
3
|
+
Use this index to match a failure signature to a previously diagnosed mechanism before opening a new implementation path. Entries are indexed by mechanism, never by test. A test name or file is evidence for a mechanism, not the identity of an entry.
|
|
4
|
+
|
|
5
|
+
When adding an entry, keep the same sections used by the seeded entries:
|
|
6
|
+
|
|
7
|
+
1. Symptom signatures
|
|
8
|
+
2. Mechanism
|
|
9
|
+
3. Affected repositories and surfaces
|
|
10
|
+
4. What fixed it
|
|
11
|
+
5. What failed and why
|
|
12
|
+
6. Current status
|
|
13
|
+
7. Evidence
|
|
14
|
+
|
|
15
|
+
## Symptom index
|
|
16
|
+
|
|
17
|
+
| Symptom signature or fingerprint | Mechanism cue | Entry |
|
|
18
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
19
|
+
| `element was detached from the DOM`, `element is not stable`, open dialog or sheet disappears during a mutation/refetch | Overlay is rendered below a query-driven mapped row that is reconciled or removed | [Query-driven list dialog teardown](./query-driven-list-dialog-teardown.md) |
|
|
20
|
+
| Dialog title or sheet content never becomes stably visible after it opened | The owning list item remounted while the overlay was open | [Query-driven list dialog teardown](./query-driven-list-dialog-teardown.md) |
|
|
21
|
+
| Fingerprints `33deef731a10`, `2d93cd67b48a` | Home Health instance of query-driven overlay ownership | [Query-driven list dialog teardown](./query-driven-list-dialog-teardown.md) |
|
|
22
|
+
| `Cognito user '<email>' does not exist`, `UserNotFoundException` immediately after a successful user-create response | Consumer is rediscovering an opaque Cognito username through an eventually consistent email alias | [Cognito email-alias eventual consistency](./cognito-email-alias-eventual-consistency.md) |
|
|
23
|
+
| Email-based Cognito Admin mutation fails while the same user exists under `from_admin_<uuid>` | Strong username identity was discarded at the producer boundary | [Cognito email-alias eventual consistency](./cognito-email-alias-eventual-consistency.md) |
|
|
24
|
+
| `TooManyRequestsException`, `MAKE_TEST_TOKEN: Too many requests`, clustered Cognito setup `401`/`403` across shards | Parallel workers exceed the shared Cognito Admin API budget | [Cognito API throttling](./cognito-api-throttling.md) |
|
|
25
|
+
| `AdminUpdateUserAttributes` throttle storm, especially from token setup | Stateful Cognito write is on a per-test or per-token hot path | [Cognito API throttling](./cognito-api-throttling.md) |
|
|
26
|
+
| Fresh entity creation succeeds, then consuming UI/API reports `Invalid workplace`, `Worker <id> not found`, opaque `500`, or readiness `422` | CDC/read-model ingestion has not reached the consuming path | [CDC and read-model readiness](./cdc-read-model-readiness.md) |
|
|
27
|
+
| Fixed CDC sleep passes usually but fails under load | Time delay is standing in for a missing readiness signal | [CDC and read-model readiness](./cdc-read-model-readiness.md) |
|
|
28
|
+
| `ChunkLoadError`, `Failed to fetch dynamically imported module`, lazy route never renders, downstream locator timeout after JS asset `5xx` | A lazy import became permanently rejected after a transient asset failure | [Lazy-chunk load recovery](./lazy-chunk-load-recovery.md) |
|
|
29
|
+
| Many test-specific lazy-route fix PRs for different pages in the same incident | Shared product loader lacks centralized recovery | [Lazy-chunk load recovery](./lazy-chunk-load-recovery.md) |
|
|
30
|
+
| Hashed JS/CSS asset returns CloudFront `503 LimitExceeded`; retries replay the same error for about 10 seconds | CloudFront error caching amplifies a short S3-origin burst | [CDN 503 amplification](./cdn-503-amplification.md) |
|
|
31
|
+
| Cold-cache deploy stampede followed by widespread static-asset failures | Hashed assets lack immutable caching and edge errors are pinned | [CDN 503 amplification](./cdn-503-amplification.md) |
|
|
32
|
+
| `LaunchDarkly initialization failed after N attempts`, app/service fails before tests start | Feature-flag bootstrap is a hard startup dependency | [Feature-flag and third-party bootstrap](./feature-flag-and-third-party-bootstrap.md) |
|
|
33
|
+
| Popup/new tab bypasses LaunchDarkly, analytics, monitoring, Maps, or other mocks | Mocks were installed on one page instead of the browser context | [Feature-flag and third-party bootstrap](./feature-flag-and-third-party-bootstrap.md) |
|
|
34
|
+
| Stripe/Maps/vendor SDK request fails, hangs, or escapes mocks before readiness UI renders | Third-party bootstrap is nondeterministic or isolated at the wrong browser scope | [Feature-flag and third-party bootstrap](./feature-flag-and-third-party-bootstrap.md) |
|
|
35
|
+
| Component test hangs after `userEvent.click/type`, especially with fake timers | Async `user-event` work is not awaited or timers cannot advance | [user-event and fake-timer timing](./user-event-and-fake-timer-timing.md) |
|
|
36
|
+
| Test passes but later throws `window is not defined`, act warning, or post-teardown timer error | Pending debounced/async work escaped teardown | [user-event and fake-timer timing](./user-event-and-fake-timer-timing.md) |
|
|
37
|
+
| Failures appeared during `@testing-library/user-event` v13 to v14 or Vitest migration | Previously synchronous assumptions became explicit promises and timer integration requirements | [user-event and fake-timer timing](./user-event-and-fake-timer-timing.md) |
|
|
38
|
+
|
|
39
|
+
## Lookup discipline
|
|
40
|
+
|
|
41
|
+
- Match the mechanism using the failure surface and causal evidence, not only a shared error string.
|
|
42
|
+
- Read the candidate entry's failed-attempt section before proposing a fix.
|
|
43
|
+
- If the evidence contradicts the entry, continue the investigation and add a new mechanism only when the causal terminus is genuinely different.
|
|
44
|
+
- Update an existing entry when a new repository or test exposes the same mechanism.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# CDC and Read-Model Readiness
|
|
2
|
+
|
|
3
|
+
Last reviewed: 2026-07-16.
|
|
4
|
+
|
|
5
|
+
## Symptom signatures
|
|
6
|
+
|
|
7
|
+
- A create API returns success, then the UI's consuming API reports `Invalid workplace`, `Worker <id> not found`, missing offer/estimate data, or an opaque `500`.
|
|
8
|
+
- A fresh shift, offer, worker, or workplace is visible in the source service but not the downstream read model.
|
|
9
|
+
- A fixed sleep usually works but fails during load or an environment incident.
|
|
10
|
+
- An outer retry re-creates fresh entities on every attempt and never lets one entity finish propagating.
|
|
11
|
+
|
|
12
|
+
## Mechanism
|
|
13
|
+
|
|
14
|
+
The test creates data in a source-of-truth service and immediately exercises a UI path backed by another service or read model. CDC, event ingestion, or asynchronous projection has not made the new entity visible to the exact consuming path yet.
|
|
15
|
+
|
|
16
|
+
Readiness must be defined by the API/state the UI will consume. A successful producer response, an arbitrary sleep, or readiness in a different store does not prove the user path is ready.
|
|
17
|
+
|
|
18
|
+
## Affected repositories and surfaces
|
|
19
|
+
|
|
20
|
+
- `cbh-admin-frontend`: fresh shift offers, rate negotiation, Daily View, and other seeded E2E flows.
|
|
21
|
+
- `clipboard-health` and downstream services such as curated shifts: producer-to-read-model ingestion boundaries.
|
|
22
|
+
- Any test helper that creates cross-service data and then navigates immediately to a consumer.
|
|
23
|
+
|
|
24
|
+
## What fixed it
|
|
25
|
+
|
|
26
|
+
- Poll the same consuming API or deterministic state used by the UI.
|
|
27
|
+
- Bound the wait and fail with the last status, body, entity IDs, trace/request metadata, and attempt count.
|
|
28
|
+
- Retry only classified readiness failures; keep genuine validation errors fast-fail.
|
|
29
|
+
- Reuse or extract one shared per-repository readiness helper for the mechanism.
|
|
30
|
+
- Keep the same entity across readiness attempts. Do not re-seed and reset the CDC clock.
|
|
31
|
+
|
|
32
|
+
[STAFF-1010](https://linear.app/clipboardhealth/issue/STAFF-1010) established the accepted pattern: poll the same offer-estimate path used by the rate-negotiation dialog instead of extending a fixed CDC sleep. [cbh-admin-frontend#6780](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/6780) applied the shared bounded helper to shift-offer setup.
|
|
33
|
+
|
|
34
|
+
## What failed and why
|
|
35
|
+
|
|
36
|
+
- Increasing the fixed CDC sleep encoded a latency guess and provided no useful exhaustion evidence.
|
|
37
|
+
- The outer HCF retry created a new workplace/worker/shift each time; every retry restarted ingestion instead of waiting for the first entity.
|
|
38
|
+
- Matching only a detailed readiness message failed when the downstream service logged the specific cause but returned an opaque `500` to the client.
|
|
39
|
+
- Retrying all `422` responses in [cbh-admin-frontend#6974](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/6974) would have made real validation errors wait 90 seconds. Review narrowed the implementation so opaque `500`s retry while unrelated `422`s still fail fast.
|
|
40
|
+
- Adding a new per-spec readiness loop duplicates policy and lets timeout/classification behavior drift. Extend the shared helper instead.
|
|
41
|
+
|
|
42
|
+
## Current status
|
|
43
|
+
|
|
44
|
+
Known recurring mechanism with a documented A1 shared-helper rule in the flaky-fix rubric. The mechanism is not globally โfixedโ because new producer/consumer boundaries continue to appear; the reusable resolution is a consuming-path readiness gate with classified failures and shared ownership.
|
|
45
|
+
|
|
46
|
+
## Evidence
|
|
47
|
+
|
|
48
|
+
- [STAFF-1010](https://linear.app/clipboardhealth/issue/STAFF-1010): canonical accepted plan for fresh shift-offer readiness.
|
|
49
|
+
- [cbh-admin-frontend#6780](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/6780): bounded shared shift-offer readiness helper.
|
|
50
|
+
- [cbh-admin-frontend#6792](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/6792): JSON:API readiness-message parsing used by the helper lineage.
|
|
51
|
+
- [cbh-admin-frontend#6974](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/6974): opaque `500` classification and reviewer-enforced `422` fast-fail behavior.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# CDN 503 Amplification for Hashed Assets
|
|
2
|
+
|
|
3
|
+
Last reviewed: 2026-07-16.
|
|
4
|
+
|
|
5
|
+
## Symptom signatures
|
|
6
|
+
|
|
7
|
+
- Hashed JavaScript or CSS assets return CloudFront `503`, often `LimitExceeded`.
|
|
8
|
+
- A short S3 website-origin error burst becomes about ten seconds of identical edge failures.
|
|
9
|
+
- Client import retries and one reload all receive the same cached error.
|
|
10
|
+
- Failures cluster immediately after deploy when E2E begins against a cold CloudFront cache.
|
|
11
|
+
- Many routes fail together because their chunks share the same distribution/origin.
|
|
12
|
+
|
|
13
|
+
## Mechanism
|
|
14
|
+
|
|
15
|
+
Deploy-time cold-cache demand creates a request stampede against the S3 website endpoint. A brief origin `5xx` is cached by CloudFront's default error TTL, so every retry replays the edge-cached error after the origin may already have recovered. Hashed assets without immutable cache headers also miss an opportunity to stay resident in browsers and at the edge.
|
|
16
|
+
|
|
17
|
+
The CDN turns a short origin blip into a wider, longer outage. Client recovery is necessary but cannot succeed while the error itself is pinned.
|
|
18
|
+
|
|
19
|
+
## Affected repositories and surfaces
|
|
20
|
+
|
|
21
|
+
- `cbh-admin-frontend`: frontend deploy action and CloudFront/S3 Terraform.
|
|
22
|
+
- `cbh-mobile-app`: HCP webapp CloudFront/S3 Terraform.
|
|
23
|
+
- Staging E2E and real browser route loads that fetch content-hashed assets through those distributions.
|
|
24
|
+
|
|
25
|
+
## What fixed it
|
|
26
|
+
|
|
27
|
+
- Set `error_caching_min_ttl = 0` for transient `500`, `502`, `503`, and `504` responses.
|
|
28
|
+
- Upload content-hashed JavaScript/CSS with `Cache-Control: public, max-age=31536000, immutable`.
|
|
29
|
+
- Preserve short/default caching for stable-named runtime/configuration files.
|
|
30
|
+
- Add per-status CloudFront metrics and access logs so the next burst can distinguish origin latency, cache behavior, and specific error codes.
|
|
31
|
+
|
|
32
|
+
The infrastructure fixes landed in [cbh-admin-frontend#7010](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/7010) and [cbh-mobile-app#12288](https://github.com/ClipboardHealth/cbh-mobile-app/pull/12288).
|
|
33
|
+
|
|
34
|
+
## What failed and why
|
|
35
|
+
|
|
36
|
+
- The central `loadLazily` retry ladder ran three imports and one reload within the period where CloudFront served the same cached error. Correct client recovery was defeated by edge policy.
|
|
37
|
+
- E2E preflight and deployed-asset graph checks from [cbh-admin-frontend#5942](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5942), [#5957](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5957), and [#5958](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5958) improved detection and reduced canary pressure but did not remove error pinning.
|
|
38
|
+
- Selective invalidation in [cbh-admin-frontend#5840](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5840) reduced deploy churn but did not change the transient-error TTL.
|
|
39
|
+
- Adding more test retries would increase the cold-cache stampede and continue replaying the cached `503`.
|
|
40
|
+
|
|
41
|
+
## Current status
|
|
42
|
+
|
|
43
|
+
Fixed in Terraform for both frontend distributions. Origin failover remains deliberately deferred; add it only if measured bursts still fail after error caching is disabled. The log-forwarding/pipeline follow-up is operational observability, not required to understand the mechanism.
|
|
44
|
+
|
|
45
|
+
## Evidence
|
|
46
|
+
|
|
47
|
+
- [STAFF-1470](https://linear.app/clipboardhealth/issue/STAFF-1470): cross-repository CDN amplification work.
|
|
48
|
+
- [cbh-admin-frontend#7010](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/7010): error TTL, immutable hashed assets, metrics, and access logs.
|
|
49
|
+
- [cbh-mobile-app#12288](https://github.com/ClipboardHealth/cbh-mobile-app/pull/12288): mobile distribution port of the infrastructure fix.
|
|
50
|
+
- [cbh-mobile-app#11898](https://github.com/ClipboardHealth/cbh-mobile-app/pull/11898): deployed-asset preflight and the linked root-cause analysis used by both Terraform fixes.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Cognito API Throttling Under Parallel Setup Load
|
|
2
|
+
|
|
3
|
+
Last reviewed: 2026-07-16.
|
|
4
|
+
|
|
5
|
+
## Symptom signatures
|
|
6
|
+
|
|
7
|
+
- `TooManyRequestsException` from `AdminUpdateUserAttributes`, `MakeTestToken`, login, or token-mint setup.
|
|
8
|
+
- `MAKE_TEST_TOKEN: Too many requests`.
|
|
9
|
+
- HCF or auth setup produces clustered `401`/`403` failures across shards while unrelated UI assertions never run.
|
|
10
|
+
- Failures correlate with worker/shard fan-out and pass on retry after the burst subsides.
|
|
11
|
+
|
|
12
|
+
## Mechanism
|
|
13
|
+
|
|
14
|
+
Parallel Playwright workers and overlapping CI runs share one staging Cognito pool and API quota. Stateful writes or token creation placed on per-test/per-token hot paths multiply across workers. In-process caches do not coordinate separate Node processes, so each worker independently repeats the same setup work.
|
|
15
|
+
|
|
16
|
+
Retries that do not reduce concurrency or call volume amplify the burst. The terminal cause is shared-capacity pressure, not the individual test that happened to request a token when the quota was exhausted.
|
|
17
|
+
|
|
18
|
+
## Affected repositories and surfaces
|
|
19
|
+
|
|
20
|
+
- `cbh-admin-frontend`: Playwright global setup, token minting, shared admin user types, dynamic worker/HCF setup.
|
|
21
|
+
- `cbh-mobile-app`: shared credential acquisition and admin-auth token caches.
|
|
22
|
+
- Any parallel test harness using the same Cognito pool or backend token endpoint without cross-process coordination.
|
|
23
|
+
|
|
24
|
+
## What fixed it
|
|
25
|
+
|
|
26
|
+
- Remove stateful Cognito writes from per-token paths and provision invariants at creation/global-setup boundaries.
|
|
27
|
+
- Deduplicate remaining writes by identity and requested attributes.
|
|
28
|
+
- Serialize or cap Cognito Admin/token operations across processes, not only within one worker.
|
|
29
|
+
- Cache shared tokens with a conservative TTL.
|
|
30
|
+
- Cap Playwright worker fan-out when the shared environment cannot support the theoretical concurrency.
|
|
31
|
+
- Preserve structured status/body/request IDs so a throttle is not misdiagnosed as an auth or UI failure.
|
|
32
|
+
|
|
33
|
+
[cbh-admin-frontend#6697](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/6697) removed per-token user-type writes; [cbh-admin-frontend#6731](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/6731) added cross-process slots, token caching, diagnostics, and a worker cap.
|
|
34
|
+
|
|
35
|
+
## What failed and why
|
|
36
|
+
|
|
37
|
+
- Frontend waits and Playwright retries did not change Cognito call volume. Repeating setup could create more token and attribute operations and worsen the throttle.
|
|
38
|
+
- Per-process throttles/caches were insufficient because each Playwright worker is a separate process.
|
|
39
|
+
- Retrying every auth/setup failure without classification hid real `401`/`403` contract errors and still amplified rate-limited operations.
|
|
40
|
+
- Treating the Upcoming Charges test as the owner was a failure-surface mistake: the observed error occurred in shared auth setup before the page loaded.
|
|
41
|
+
|
|
42
|
+
## Current status
|
|
43
|
+
|
|
44
|
+
Mitigated for the known admin staging setup paths. Cross-process coordination and removal of hot-path writes are the reusable fix class. Re-open the capacity question only after measuring call volume with these controls; quota increases or a dedicated E2E pool are secondary options, not the first fix.
|
|
45
|
+
|
|
46
|
+
## Evidence
|
|
47
|
+
|
|
48
|
+
- [STAFF-1122](https://linear.app/clipboardhealth/issue/STAFF-1122): canonical investigation; CloudTrail evidence included 980 throttled `AdminUpdateUserAttributes` calls.
|
|
49
|
+
- [cbh-admin-frontend#6697](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/6697): removed per-token Cognito writes and deduplicated remaining setup mutations.
|
|
50
|
+
- [STAFF-1169](https://linear.app/clipboardhealth/issue/STAFF-1169): follow-up for broader staging auth fan-out.
|
|
51
|
+
- [cbh-admin-frontend#6731](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/6731): shared cross-process throttling, caching, worker cap, and diagnostics.
|
package/skills/flaky-debug/references/root-cause-kb/cognito-email-alias-eventual-consistency.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Cognito Email-Alias Eventual Consistency
|
|
2
|
+
|
|
3
|
+
Last reviewed: 2026-07-16.
|
|
4
|
+
|
|
5
|
+
## Symptom signatures
|
|
6
|
+
|
|
7
|
+
- `Cognito user '<email>' does not exist in pool '<pool>'` immediately after the user-create API returned success.
|
|
8
|
+
- `AdminSetUserPassword` or `AdminUpdateUserAttributes` raises `UserNotFoundException` when called with an email.
|
|
9
|
+
- A bounded retry sometimes passes under light load and fails as a storm under overlapping CI runs.
|
|
10
|
+
- Direct lookup by the opaque username succeeds while lookup or mutation by email still fails.
|
|
11
|
+
|
|
12
|
+
## Mechanism
|
|
13
|
+
|
|
14
|
+
Backend user creation stores Cognito users under opaque usernames such as `from_admin_<uuid>` and stores the email as an attribute/alias. When the producer discards that username, the test harness must rediscover the identity through Cognito's email-alias index. That lookup path is eventually consistent; the strongly consistent username path is already available but was not returned across the API contract.
|
|
15
|
+
|
|
16
|
+
This mechanism is distinct from Cognito throttling. Throttling can lengthen the propagation tail, but a `UserNotFoundException` from email-based resolution after a proven successful create is an identity-contract mismatch.
|
|
17
|
+
|
|
18
|
+
## Affected repositories and surfaces
|
|
19
|
+
|
|
20
|
+
- `clipboard-health`: workplace/facility user creation and the backend-main contract.
|
|
21
|
+
- `cbh-admin-frontend`: Playwright password and user-type provisioning for freshly created synthetic users.
|
|
22
|
+
- Other consumers that create a Cognito identity through backend-main and then address Cognito Admin APIs by email.
|
|
23
|
+
|
|
24
|
+
## What fixed it
|
|
25
|
+
|
|
26
|
+
- Return the opaque Cognito username from the user-creation response and contract.
|
|
27
|
+
- Use that username for subsequent Cognito Admin mutations instead of resolving by email.
|
|
28
|
+
- Fail loudly or emit explicit telemetry when Cognito provisioning fails, rather than returning an apparently usable user with no identity.
|
|
29
|
+
- Keep a readiness probe only as a migration guard for producer paths that cannot yet return the username.
|
|
30
|
+
|
|
31
|
+
The producer-side contract fix landed in [clipboard-health#26862](https://github.com/ClipboardHealth/clipboard-health/pull/26862) for [STAFF-1793](https://linear.app/clipboardhealth/issue/STAFF-1793).
|
|
32
|
+
|
|
33
|
+
## What failed and why
|
|
34
|
+
|
|
35
|
+
- Five `UserNotFoundException` retries spaced two seconds apart guessed that email propagation would finish within ten seconds. The guess usually held, then failed under load.
|
|
36
|
+
- The 75โ90 second readiness implementation in [cbh-admin-frontend#7380](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/7380) was useful diagnostic armor but remained consumer-side: it still paid the eventually consistent email-resolution cost and added Cognito Admin calls.
|
|
37
|
+
- Increasing the wait cannot fix a swallowed producer failure where no Cognito identity was created. The producer must distinguish absence from propagation.
|
|
38
|
+
- Treating the whole storm as throttling alone would preserve the incorrect email-based identity contract.
|
|
39
|
+
|
|
40
|
+
## Current status
|
|
41
|
+
|
|
42
|
+
The workplace-user producer contract now exposes the strongly consistent username. Consumer paths should delete email-resolution probes as they adopt the contract. Keep this entry active until all fresh-user setup paths, including worker/HCP variants, use a returned username or another deterministic identity.
|
|
43
|
+
|
|
44
|
+
## Evidence
|
|
45
|
+
|
|
46
|
+
- [STAFF-1667](https://linear.app/clipboardhealth/issue/STAFF-1667): storm family with six tests and the shared `Cognito user does not exist` signature.
|
|
47
|
+
- [STAFF-1672](https://linear.app/clipboardhealth/issue/STAFF-1672): consumer-side readiness design.
|
|
48
|
+
- [cbh-admin-frontend#7380](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/7380): closed readiness implementation and its bounded-wait diagnostics.
|
|
49
|
+
- [STAFF-1793](https://linear.app/clipboardhealth/issue/STAFF-1793): producer-side root fix.
|
|
50
|
+
- [clipboard-health#26862](https://github.com/ClipboardHealth/clipboard-health/pull/26862): returned username, fail-loud behavior, cleanup, and metrics.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Feature-Flag and Third-Party Bootstrap Nondeterminism
|
|
2
|
+
|
|
3
|
+
Last reviewed: 2026-07-16.
|
|
4
|
+
|
|
5
|
+
## Symptom signatures
|
|
6
|
+
|
|
7
|
+
- `LaunchDarkly initialization failed after N attempts` aborts application or service startup.
|
|
8
|
+
- A vendor SDK/API request for feature flags, Stripe, Maps, analytics, or monitoring fails or hangs before expected UI renders.
|
|
9
|
+
- A popup/new tab makes real third-party requests even though the original page was mocked.
|
|
10
|
+
- CORS errors or default flag values are followed by unauthorized or wrong-route API calls.
|
|
11
|
+
- The final test failure is a missing button/locator, but the first divergence is an external bootstrap request.
|
|
12
|
+
|
|
13
|
+
## Mechanism
|
|
14
|
+
|
|
15
|
+
Application readiness depends on third-party initialization whose availability, page scope, or completion timing is nondeterministic.
|
|
16
|
+
|
|
17
|
+
There are two important subtypes:
|
|
18
|
+
|
|
19
|
+
1. A true runtime dependency such as LaunchDarkly may intentionally be hard. Failing open is safe only when every flag has a verified safe default and the client will recover correctly.
|
|
20
|
+
2. E2E-only external dependencies should be isolated at the right scope. Page-scoped mocks do not apply to a popup/new tab, and route navigation is not ready merely because the DOM content loaded event fired.
|
|
21
|
+
|
|
22
|
+
## Affected repositories and surfaces
|
|
23
|
+
|
|
24
|
+
- `clipboard-health`: NestJS feature-flag provider startup.
|
|
25
|
+
- `cbh-admin-frontend`: facility onboarding popups, LaunchDarkly/analytics/monitoring/Places dependencies.
|
|
26
|
+
- `cbh-mobile-app`: vendor-backed onboarding and post-auth route bootstraps when the external integration is the earliest failing dependency.
|
|
27
|
+
- Any browser test that opens another page or relies on external scripts before rendering route readiness UI.
|
|
28
|
+
|
|
29
|
+
## What fixed it
|
|
30
|
+
|
|
31
|
+
- Install E2E mocks on `browserContext.route()` when every page/tab in the context must inherit them.
|
|
32
|
+
- Register popup/new-page waits before the click, then wait for a product readiness signal on the new page.
|
|
33
|
+
- For vendor bootstraps, wait on an explicit SDK or product readiness signal and report the failed external request.
|
|
34
|
+
- Mock third-party behavior when it is not the system under test, while preserving the real contract shape.
|
|
35
|
+
- Decide LaunchDarkly degraded behavior explicitly from product safety, not from test convenience.
|
|
36
|
+
|
|
37
|
+
[cbh-admin-frontend#5989](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5989) moved feature-flag and third-party mocks to the browser context for popup coverage.
|
|
38
|
+
|
|
39
|
+
## What failed and why
|
|
40
|
+
|
|
41
|
+
- The blanket LaunchDarkly fail-open proposal in [clipboard-health#26748](https://github.com/ClipboardHealth/clipboard-health/pull/26748) was closed. Review established that defaults are not reliably safe and an initialized client might not recover in the intended way, so making LaunchDarkly optional could produce a broader incorrect-state outage.
|
|
42
|
+
- Page-scoped `page.route()` mocks passed on the original page but leaked real LaunchDarkly and third-party requests from a newly opened tab.
|
|
43
|
+
- Waiting only for navigation or the DOM content loaded event treated the HTML document as application readiness; a vendor SDK or flag bootstrap could still prevent React from rendering.
|
|
44
|
+
- Longer locator timeouts hid the external request that failed and provided no bootstrap diagnostics.
|
|
45
|
+
- Mocking away the third party is wrong when that integration is the behavior under test; use contract-faithful mocks only for dependencies outside the scenario.
|
|
46
|
+
- Do not classify every Stripe- or Maps-named test as this mechanism. [STAFF-1486](https://linear.app/clipboardhealth/issue/STAFF-1486) and [cbh-mobile-app#12309](https://github.com/ClipboardHealth/cbh-mobile-app/pull/12309) were static-asset `503` failures on a Stripe route, so they belong to the lazy-chunk/CDN entries.
|
|
47
|
+
|
|
48
|
+
## Current status
|
|
49
|
+
|
|
50
|
+
Mixed by dependency. Browser-context mock scope and explicit vendor readiness signals are the established test patterns. LaunchDarkly remains an intentional hard dependency in backend-main until safe defaults and recovery semantics are proven; do not cite this KB entry as permission to swallow its initialization errors.
|
|
51
|
+
|
|
52
|
+
## Evidence
|
|
53
|
+
|
|
54
|
+
- [cbh-admin-frontend#5989](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5989): popup failure caused by page-scoped LaunchDarkly/third-party mocks.
|
|
55
|
+
- [clipboard-health#26748](https://github.com/ClipboardHealth/clipboard-health/pull/26748): rejected LaunchDarkly fail-open attempt and reviewer rationale.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Lazy-Chunk Load Failures Need Product-Side Recovery
|
|
2
|
+
|
|
3
|
+
Last reviewed: 2026-07-16.
|
|
4
|
+
|
|
5
|
+
## Symptom signatures
|
|
6
|
+
|
|
7
|
+
- `ChunkLoadError`.
|
|
8
|
+
- `Failed to fetch dynamically imported module`.
|
|
9
|
+
- A JavaScript asset request returns `5xx`, then a lazy route never renders.
|
|
10
|
+
- Playwright times out on a page locator even though the earliest failure is a static chunk request.
|
|
11
|
+
- Multiple routes/tests fail with different selectors during the same asset incident.
|
|
12
|
+
|
|
13
|
+
## Mechanism
|
|
14
|
+
|
|
15
|
+
A transient static-asset failure rejects a dynamic import. The lazy loader or route remains stuck behind the rejected import/error boundary even after the origin recovers. Downstream locator failures are secondary because the route's code never loaded.
|
|
16
|
+
|
|
17
|
+
The recovery belongs in the shared product loader: classify chunk-load failures and perform one guarded reload for the affected app version, commit, page, and session. The test may add diagnostics, but it should not be the only recovery layer.
|
|
18
|
+
|
|
19
|
+
## Affected repositories and surfaces
|
|
20
|
+
|
|
21
|
+
- `cbh-admin-frontend`: React lazy routes through the central `loadLazily` wrapper.
|
|
22
|
+
- `cbh-mobile-app`: Playwright route-bootstrap and static-asset recovery helpers; these protect tests, not real users.
|
|
23
|
+
- Any deployed SPA using content-hashed chunks that may briefly fail during deploy/origin/CDN incidents.
|
|
24
|
+
|
|
25
|
+
## What fixed it
|
|
26
|
+
|
|
27
|
+
- Centralize recognition of browser chunk-load/dynamic-import failures.
|
|
28
|
+
- Retry the import as appropriate, then reload at most once using a session guard scoped to app version, commit, and route.
|
|
29
|
+
- Fall through to the normal error boundary after the bounded recovery is exhausted.
|
|
30
|
+
- Log whether recovery was attempted, skipped by the guard, or exhausted.
|
|
31
|
+
|
|
32
|
+
[cbh-admin-frontend#5650](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5650) is the canonical product fix. Mobile later added Playwright-only static-asset route-bootstrap armor in [cbh-mobile-app#11958](https://github.com/ClipboardHealth/cbh-mobile-app/pull/11958) and reused it on the Stripe-named asset failure in [cbh-mobile-app#12309](https://github.com/ClipboardHealth/cbh-mobile-app/pull/12309).
|
|
33
|
+
|
|
34
|
+
## What failed and why
|
|
35
|
+
|
|
36
|
+
- Five sibling PRs ([#5645](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5645), [#5646](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5646), [#5647](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5647), [#5648](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5648), and [#5649](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5649)) treated individual routes/tests as separate fixes. They were consolidated because the mechanism was shared.
|
|
37
|
+
- Per-test route-ready retries and chunk monitors can improve diagnostics but leave real users stuck when the lazy loader itself has no recovery.
|
|
38
|
+
- Unbounded reloads can create a loop during a persistent deploy or asset mismatch. Recovery must be one-time and version/route scoped.
|
|
39
|
+
- Loader recovery alone could not defeat a CDN that cached the same `503` for every retry; that is the separate [CDN amplification mechanism](./cdn-503-amplification.md).
|
|
40
|
+
|
|
41
|
+
## Current status
|
|
42
|
+
|
|
43
|
+
Product-side recovery is merged for the known admin loader. Mobile's cited recovery is test-harness-only, so a real-user mobile web failure still needs a product-side loader diagnosis. Future per-route sightings should first verify whether the surface has product recovery, harness armor, or neither, and whether the asset failure is transient. Do not create one implementation per test.
|
|
44
|
+
|
|
45
|
+
## Evidence
|
|
46
|
+
|
|
47
|
+
- [cbh-admin-frontend#5645](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5645): representative closed per-surface attempt that mixed product and E2E recovery.
|
|
48
|
+
- [cbh-admin-frontend#5650](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5650): merged centralized lazy-chunk recovery.
|
|
49
|
+
- [cbh-admin-frontend#5492](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/5492): earlier chunk-load logging and user-facing refresh behavior.
|
|
50
|
+
- [cbh-mobile-app#11958](https://github.com/ClipboardHealth/cbh-mobile-app/pull/11958): Playwright app-bootstrap static-asset retry helper.
|
|
51
|
+
- [cbh-mobile-app#12309](https://github.com/ClipboardHealth/cbh-mobile-app/pull/12309): Stripe-route sighting whose actual cause was CloudFront/static assets, not Stripe bootstrap.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Query-Driven List Dialog Teardown
|
|
2
|
+
|
|
3
|
+
Last reviewed: 2026-07-16.
|
|
4
|
+
|
|
5
|
+
## Symptom signatures
|
|
6
|
+
|
|
7
|
+
- Playwright reports `element is not stable` followed by `element was detached from the DOM`.
|
|
8
|
+
- A dialog or sheet opens, then its title/content disappears or never becomes stably visible.
|
|
9
|
+
- The failure moves between actions on different rows of the same query-backed list.
|
|
10
|
+
- A mutation succeeds, but the confirmation interaction or post-success assertion loses the overlay.
|
|
11
|
+
- Known families: `33deef731a10` and `2d93cd67b48a`.
|
|
12
|
+
|
|
13
|
+
## Mechanism
|
|
14
|
+
|
|
15
|
+
The overlay is owned by a component rendered beneath a mapped query result, such as `cases.map(...<CaseCard>)` or `visits.map(...<VisitCard>)`. Background, focus, interval, or mutation-triggered query refreshes reconcile, reorder, or remove the row while the overlay is open. Because the dialog or sheet lifecycle is tied to that row, the overlay remounts or unmounts during interaction.
|
|
16
|
+
|
|
17
|
+
The decisive test is structural: if deleting or replacing the launching row also destroys the open overlay, the product has this mechanism even when the visible failure is a locator timeout.
|
|
18
|
+
|
|
19
|
+
## Affected repositories and surfaces
|
|
20
|
+
|
|
21
|
+
- `cbh-admin-frontend`: Home Health case and visit action dialogs; other query-driven list dialogs found by the repository sweep.
|
|
22
|
+
- `cbh-mobile-app`: placement sign-on-bonus and badge-history sheets; workplace-review comment and reply action sheets.
|
|
23
|
+
- Any React surface that renders modal state and overlay JSX inside a query-backed `.map()` descendant.
|
|
24
|
+
|
|
25
|
+
## What fixed it
|
|
26
|
+
|
|
27
|
+
- Hoist overlay ownership above the mapped list into one stable, entity-keyed host. Rows become trigger-only and pass entity identity to the host.
|
|
28
|
+
- Add regression coverage that opens the overlay, removes or refreshes the launching row, and proves the overlay survives.
|
|
29
|
+
- Add an architecture guard for direct dialog/modal/sheet JSX inside `.map()` callbacks, then perform a semantic sweep for indirect descendants the syntax guard cannot see.
|
|
30
|
+
- Split oversized sequential E2E flows after the product fix so one mechanism does not surface as many indistinguishable steps.
|
|
31
|
+
|
|
32
|
+
The durable Home Health fix is [cbh-admin-frontend#7574](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/7574). Repository-wide follow-ups landed in [cbh-admin-frontend#7569](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/7569), [cbh-mobile-app#12857](https://github.com/ClipboardHealth/cbh-mobile-app/pull/12857), and [cbh-mobile-app#12860](https://github.com/ClipboardHealth/cbh-mobile-app/pull/12860).
|
|
33
|
+
|
|
34
|
+
## What failed and why
|
|
35
|
+
|
|
36
|
+
- Repeated `toPass` blocks, response waits, locator retries, failure trackers, and expanded timeouts hardened whichever Home Health step failed most recently but left overlay ownership unchanged. The family recurred across [STAFF-1329](https://linear.app/clipboardhealth/issue/STAFF-1329), [STAFF-1369](https://linear.app/clipboardhealth/issue/STAFF-1369), [STAFF-1495](https://linear.app/clipboardhealth/issue/STAFF-1495), [STAFF-1554](https://linear.app/clipboardhealth/issue/STAFF-1554), [STAFF-1582](https://linear.app/clipboardhealth/issue/STAFF-1582), [STAFF-1673](https://linear.app/clipboardhealth/issue/STAFF-1673), and [STAFF-1685](https://linear.app/clipboardhealth/issue/STAFF-1685).
|
|
37
|
+
- Preserving modal state across a remount did not preserve the DOM node Playwright or a user was interacting with. The Team Members recurrence from [STAFF-1422](https://linear.app/clipboardhealth/issue/STAFF-1422) to [STAFF-1459](https://linear.app/clipboardhealth/issue/STAFF-1459) is the precedent.
|
|
38
|
+
- Pausing backing-list observers and deferring invalidations in [cbh-admin-frontend#7559](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/7559) reduced the race window but was explicitly an interim mitigation: another ancestor update could still remove the row. The hoisted host later deleted that workaround.
|
|
39
|
+
- Splitting the E2E test alone would reduce blast radius and improve fingerprints, but it would not fix the user-facing overlay teardown.
|
|
40
|
+
|
|
41
|
+
## Current status
|
|
42
|
+
|
|
43
|
+
Fixed for the known admin and mobile surfaces covered by [STAFF-1789](https://linear.app/clipboardhealth/issue/STAFF-1789), [STAFF-1790](https://linear.app/clipboardhealth/issue/STAFF-1790), [STAFF-1796](https://linear.app/clipboardhealth/issue/STAFF-1796), [STAFF-1797](https://linear.app/clipboardhealth/issue/STAFF-1797), and [STAFF-1806](https://linear.app/clipboardhealth/issue/STAFF-1806). Treat future instances as the same mechanism when overlay ownership is still below a replaceable query row; extend the stable-host pattern and guard rather than adding per-test waits.
|
|
44
|
+
|
|
45
|
+
## Evidence
|
|
46
|
+
|
|
47
|
+
- [STAFF-1789](https://linear.app/clipboardhealth/issue/STAFF-1789): mitigation and fault-injection proof for Home Health list refreshes.
|
|
48
|
+
- [STAFF-1790](https://linear.app/clipboardhealth/issue/STAFF-1790): durable host design and E2E decomposition.
|
|
49
|
+
- [cbh-admin-frontend#7559](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/7559): refetch guard, with residual ownership risk documented.
|
|
50
|
+
- [cbh-admin-frontend#7574](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/7574): hoisted host and deletion of the temporary guard.
|
|
51
|
+
- [cbh-mobile-app#12857](https://github.com/ClipboardHealth/cbh-mobile-app/pull/12857): mobile sheet hoists and architecture guard.
|
|
52
|
+
- [cbh-mobile-app#12860](https://github.com/ClipboardHealth/cbh-mobile-app/pull/12860): indirect workplace-review descendants hoisted after the syntactic guard could not detect them.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# user-event and Fake-Timer Component-Test Timing
|
|
2
|
+
|
|
3
|
+
Last reviewed: 2026-07-16.
|
|
4
|
+
|
|
5
|
+
## Symptom signatures
|
|
6
|
+
|
|
7
|
+
- Component test hangs or times out after `userEvent.click`, `type`, `clear`, or keyboard input.
|
|
8
|
+
- Assertions run before event-driven state updates complete.
|
|
9
|
+
- Fake-timer suites stop progressing because `user-event` scheduled work cannot advance.
|
|
10
|
+
- A test passes, then emits an act warning, unhandled rejection, or `window is not defined` after jsdom teardown.
|
|
11
|
+
- Failures appear after a `user-event`, Vitest, React, or Testing Library upgrade exposes previously implicit timing.
|
|
12
|
+
|
|
13
|
+
## Mechanism
|
|
14
|
+
|
|
15
|
+
`@testing-library/user-event` v14 APIs are asynchronous and model multiple browser events. Tests written with v13-style synchronous calls can assert or tear down before those events finish. When fake timers are active, the configured `user-event` instance must be able to advance the runner's timers; otherwise its internal delays and the component's scheduled work deadlock.
|
|
16
|
+
|
|
17
|
+
A related teardown class occurs when debounced component-library timers remain pending after the DOM environment is destroyed. Timer ownership and cleanup, not a larger timeout, determine correctness.
|
|
18
|
+
|
|
19
|
+
## Affected repositories and surfaces
|
|
20
|
+
|
|
21
|
+
- `cbh-mobile-app`: Jest and Vitest component tests migrated from `user-event` v13.5 to v14.
|
|
22
|
+
- `cbh-admin-frontend`: component-library debounces and fake-timer teardown patterns.
|
|
23
|
+
- Any component test mixing `user-event`, fake timers, MSW, debounced UI libraries, animations, or scheduled state updates.
|
|
24
|
+
|
|
25
|
+
## What fixed it
|
|
26
|
+
|
|
27
|
+
- Upgrade to `user-event` v14 and await every interaction.
|
|
28
|
+
- Prefer one `const user = userEvent.setup(...)` instance per test.
|
|
29
|
+
- In fake-timer suites, configure `advanceTimers` with the active runner, such as `vi.advanceTimersByTime` or `jest.advanceTimersByTime`.
|
|
30
|
+
- Flush only the work the scenario owns, restore real timers in `afterEach`, and keep cleanup before jsdom teardown.
|
|
31
|
+
- Enforce `testing-library/await-async-events` so new interactions that are not awaited fail lint.
|
|
32
|
+
|
|
33
|
+
The repository-wide migration landed in [cbh-mobile-app#10310](https://github.com/ClipboardHealth/cbh-mobile-app/pull/10310).
|
|
34
|
+
|
|
35
|
+
## What failed and why
|
|
36
|
+
|
|
37
|
+
- [cbh-mobile-app#5565](https://github.com/ClipboardHealth/cbh-mobile-app/pull/5565) attempted a broad `fireEvent` to `userEvent` lint conversion before the full async/timer migration. It was closed with unresolved lint and test failures; changing the API name alone did not establish correct timing.
|
|
38
|
+
- Static `userEvent.*` calls mixed with `userEvent.setup()` left inconsistent isolation and made missing awaits easy to reintroduce.
|
|
39
|
+
- Adding per-test or global timeout increases, including the Vitest 4 migration's timeout accommodation in [cbh-mobile-app#11947](https://github.com/ClipboardHealth/cbh-mobile-app/pull/11947), can make slow suites pass but does not fix a deadlocked fake timer or interaction that was not awaited.
|
|
40
|
+
- Fake timers alone are not sufficient. Without `advanceTimers`, `user-event` may wait forever; without teardown flushing/restoration, callbacks may fire after jsdom is gone.
|
|
41
|
+
- The closed [cbh-admin-frontend#6699](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/6699) correctly identified a pending MUI debounce but never landed, illustrating that a plausible timer diagnosis still needs a maintained, merged cleanup pattern.
|
|
42
|
+
|
|
43
|
+
## Current status
|
|
44
|
+
|
|
45
|
+
The mobile v14 migration and lint enforcement are merged. Treat new component-test timing failures as one of three explicit subtypes before editing: interaction not awaited, fake-timer integration, or pending teardown work. Do not default to timeout inflation.
|
|
46
|
+
|
|
47
|
+
## Evidence
|
|
48
|
+
|
|
49
|
+
- [cbh-mobile-app#10310](https://github.com/ClipboardHealth/cbh-mobile-app/pull/10310): v13.5 to v14 migration, approximately 1,300 awaited calls, fake-timer `advanceTimers` setup, and lint enforcement.
|
|
50
|
+
- [cbh-mobile-app#5565](https://github.com/ClipboardHealth/cbh-mobile-app/pull/5565): incomplete earlier mechanical conversion.
|
|
51
|
+
- [cbh-mobile-app#11947](https://github.com/ClipboardHealth/cbh-mobile-app/pull/11947): Vitest 4 migration showing remaining user-event/fake-timer/MSW timing pressure.
|
|
52
|
+
- [cbh-admin-frontend#6699](https://github.com/ClipboardHealth/cbh-admin-frontend/pull/6699): post-teardown MUI debounce signature and proposed timer cleanup.
|
|
@@ -68,11 +68,13 @@ Do not merge clusters merely because failures share a run. Same-run failures can
|
|
|
68
68
|
4. **Multi-ticket cluster** โ pick the canonical (richest evidence: most failures, clearest artifacts). Comment the cluster manifest on it: member IDs, shared mechanism, evidence tier from Phase 3, and the marker `<!-- flaky-triage: cluster=<short-slug> -->`. Close the other members as Duplicate of the canonical (D2 format). Release the canonical.
|
|
69
69
|
5. **Singleton or uncertain** โ release as-is with a brief `<!-- flaky-triage: singleton -->` comment.
|
|
70
70
|
|
|
71
|
+
Before any action above releases a canonical ticket, search Linear for the fingerprint family with the `flaky-implementation` label across all states and without a date-window restriction, then count unique prior implementation ticket IDs. At **โฅ3 prior implementation tickets**, do not release the canonical for normal dispatch and leave it in `Triage` unassigned. Create the Linear `chronic` label first if it does not exist. Apply the `chronic` label and hold the ticket for the deep-dive track (a dedicated dossier-first investigation stage; until it exists, chronic tickets stay held with the label for human routing). Add a ticket comment with the marker `<!-- flaky-triage: chronic -->` and dossier pointers listing every prior implementation ticket ID โ but first search the ticket's existing comments for that exact marker and skip posting when it is already present; that absence check is what makes the comment idempotent across retries and re-runs. This โฅ3-ticket count is a cheap screen staged before any diagnosis exists; the critic separately applies a sharper **โฅ2 failed merged fixes** test (see the flaky-critic rubric's Prior-attempts amendment) with the same chronic routing. The thresholds differ by design โ by the evidence available at each stage โ not by accident.
|
|
72
|
+
|
|
71
73
|
**Release** = set state to `Todo` and assign to the Linear API user (the viewer). Before release, ensure the ticket satisfies the Groundcrew eligibility contract from `../create-groundcrew-ticket/SKILL.md`: repository text in the description, exactly one `agent-*` label, leaf issue, and no non-terminal blockers unless intentionally blocked. Assignment + Todo is what makes groundcrew dispatch it; it also serves as the lease โ released tickets never re-enter the queue.
|
|
72
74
|
|
|
73
75
|
## Phase 5: Digest
|
|
74
76
|
|
|
75
|
-
End with a compact report: queue size, cluster count with members and evidence tiers, released / duplicated / canceled counts, in-flight matches found, and risks (possible false merges, missing evidence). When run as a recurring task, follow any re-arm instructions in the task description after the digest.
|
|
77
|
+
End with a compact report: queue size, cluster count with members and evidence tiers, released / duplicated / canceled / chronic-held counts, in-flight matches found, chronic detections with each canonical ticket and its prior implementation ticket IDs, and risks (possible false merges, missing evidence). When run as a recurring task, follow any re-arm instructions in the task description after the digest.
|
|
76
78
|
|
|
77
79
|
## Dry-run and backtest
|
|
78
80
|
|