agentsclimarketplace

Dejavu error triage

Skill himattm/dejavu/.claude/skills/dejavu-error-triage

Diagnose and fix a Dejavu test failure. Use when a Compose UI test failed with UnexpectedRecompositionsError, when CI or a local gradle/IDE test run shows a Dejavu assertion failure, when the user pastes the failure output (sections like "Possible cause", "Recomposition timeline", "All tracked composables"), or when the user asks why a Dejavu test is failing and how to fix it — without setting up an iterative optimization loop.From its SKILL.md

Install
npx -y skills add himattm/dejavu --skill dejavu-error-triage

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

6.8 KB, as published. Nobody here has run it

Dejavu Error Triage

A Compose UI test failed with UnexpectedRecompositionsError. This skill walks the failure output section-by-section, names the underlying recomposition pattern, and points at the canonical fix. Apply the fix, re-run the failing test, done — no iteration loop.

If the goal is iterative optimization (loose atMost = N baseline → tighten to assertStable()), use the dejavu-perf-loop skill instead. If no Dejavu test exists yet, use dejavu-test-writer first.

Reference docs (read first)

  • docs/error-messages.md — full anatomy of the error block (sections 1–7, lines 11–40 for the canonical example) and "Common Failure Patterns" 1–5 (lines 168–256).
  • docs/causality-analysis.md — what each "Possible cause" line means, including same-value writes and dirty-bit signals.

Locate the failure in the test output

The error block is what the test runner (gradle, Robolectric, the IDE runner, CI logs) prints when a Dejavu assertion throws. Look for:

  • dejavu.UnexpectedRecompositionsError: — the exception class line.
  • The failing test method (e.g. MyTest > assertionFailed FAILED).
  • The full multi-line block that follows, ending with the semantic tree dump.

Capture the complete block — every section is diagnostic input. Truncated output (just the exception message) hides the cause. If the user pasted only a header, ask for the full output.

Read the error in this order

docs/error-messages.md "Reading the Error Quickly" (lines 260–269) prescribes this; follow it strictly:

  1. Expected vs Actual — quantify the gap (e.g. expected 0, actual 3).
  2. All tracked composables — find the <-- FAILED marker. If the parent's count equals the failed child's count, you have a parent cascade.
  3. Possible cause — same-value write is an immediate bug; Parameter/parent change detected (dirty bits set) means dirty bits fired.
  4. Recomposition timeline — param slots changed: [N] tells you which parameter's dirty bit fired (slot 0 = first parameter).
  5. Composable: name (File.kt:NN) — jump to source.

Don't guess. Read all five before naming a cause.

Diagnosis → fix table

Apply the first matching row. After applying, re-run the failing test once.

#Signal in the errorDiagnosisFix
1Possible cause: includes same-value writeSnapshot fired an apply notification even though the new value equaled the old one, so the consumer recomposed unnecessarilydata class for the state holder, OR mutableStateOf(value, policy = structuralEqualityPolicy()), OR guard write site with if (newValue != state.value) state.value = newValue
2Parameter/parent change detected (dirty bits set) AND param slots changed: [N] AND the param at slot N is a non-data-classReference equality on an unstable type; new instance ≠ old instance even with identical fieldsConvert param's class to data class, or annotate @Immutable / @Stable
3Multiple recompositions on a single interaction; param type broader than what the function actually uses (e.g. Int only used in > 0)Type granularity is too coarseNarrow the parameter type (Int → Boolean, List → Boolean/size)
4Same as #3 but the consumer reads a fine-grained value at every parent recomposition AND a derived signal flips less oftenFine-grained state should be coalesced before the boundaryval coarse by remember { derivedStateOf { fineState.someProperty } }; pass coarse
5Parent and many siblings have the same count in All tracked composablesChild cascades whenever parent recomposes for unrelated reasonsHoist the state read into the leaf composable that needs it; or pass State<T> and read .value in the consumer
6Same composable repeats in the timeline once per list item, no key() block in the sourceCompose can't track item identity across reorderingsitems(list, key = { it.id }); or key(it.id) { … }
7Many siblings recompose together because parent re-runs for a coarse reasonParent state is too broad; siblings inherit the cascadeMove the state into a CompositionLocal; or split parent into a stable shell + a state-reading inner composable
8Recomposition timeline shows #1, #2, #3 at increasing timestamps with different param slots changed slotsMultiple independent state writes, each invalidating different slotsBatch with Snapshot.withMutableSnapshot { … }; consolidate related state into one object; or use derivedStateOf to coalesce
?Pattern doesn't matchDon't guessRe-read docs/error-messages.md "Common Failure Patterns" 1–5 (lines 168–256) and docs/causality-analysis.md summary table (lines 251–257) before suggesting a fix

Special cases

  • Warning: testTag '...' could not be mapped to a composable function — the tag is on a framework-internal node. Move Modifier.testTag() to the outermost user-defined composable's modifier. (docs/error-messages.md Pattern 3.)
  • Actual: composable was never composed or isn't being tracked — the tag doesn't exist in the composition. For lazy lists, scroll the item into view before asserting. (docs/error-messages.md Pattern 5.)
  • IllegalArgumentException from the assertion itself — invalid parameter combination (e.g. exactly = 1, atLeast = 1). Fix the assertion call, not the composable.

Wrap-up

Report back to the user with:

  1. The diagnosis — quote the specific signal that matched (e.g. "row #2: Parameter/parent change detected + param slots changed: [0] + class CartSummary is not a data class").
  2. The recommended fix — one specific, applicable change with the file and line.
  3. Whether to apply it now — only edit code if the user explicitly asked for a fix, not just a diagnosis. If you're unsure, ask.
  4. How to verify — re-run the specific failing test, not the full suite. For gradle: ./gradlew :<module>:<task> --tests "<ClassName>.<testName>". The fix worked iff the assertion that previously threw now passes and no other asserts regressed.
  5. Whether iteration is needed — if the count is much higher than expected (e.g. expected exactly = 0, actual 7) and one fix likely won't get all the way to the floor, recommend invoking dejavu-perf-loop instead of guessing the next fix.

Keep looking

Skills are one crate of 628,971. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.