Skip to content

fix(tests): diagnose the observe-test hang and bring the merge shard under budget (0507) - #396

Merged
danielhanold merged 11 commits into
mainfrom
fix/flaky-tests-track-and-stabilize-intermittent-suite-failures
Oct 5, 2026
Merged

danielhanold merged 11 commits into
mainfrom
fix/flaky-tests-track-and-stabilize-intermittent-suite-failures

Conversation

@danielhanold

@danielhanold danielhanold commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

↩ Change 0507 — Fix the observe-test hang and bring two test files back under their time limits

Summary

Change 0507: fixes and records the four suite problems it was opened for.

  • Observe-test hang (internal/process TestObserveRunningThenTerminal): not reproduced in 4,000 iterations under two load profiles. The test no longer discards the readManifest / signalGroup errors, and on the unchanged 30s expiry it now reports what was stuck (last Observe state/error, live group members, live-lock holder). Root cause stays open.
  • tests/test_go_integration_app_merge.sh: 42.6s -> 10.0s quiet solo median. The merge tests run in parallel via a planning-node builder that isolates the global config dir once per process (new mutation-tested isolation test). Budget row lowered 30 -> 15 by the sizing rule.
  • tests/test_go_finalize_e2e.sh: every candidate cut was measured and rejected; no code change. Medians on the identical tree were 23-28.2s. Human decision: keep the 30s row or apply the spec fallback (35s). See the results file.
  • tests/test_go_race.sh: 44.6s quiet solo median against its 60s row. Recorded only.

Measurements and methodology: see the results file and the empty record commits on the branch.

Review

Whole-branch review (deep tier): 4 findings.

# Severity Finding Disposition
1 important Results presented the e2e 24.3s median as meeting the target; same-tree medians are 24.3-28.2s fixed (results corrected; row decision flagged for a human)
2 important Results did not address the gate's budget report fixed (results now records every line from the gates)
3 minor Merge row left at 30s after the file dropped to 10s fixed in 76278cd (row 30 -> 15)
4 minor Reproduction ran narrower than the spec's bound, unstated fixed (results states the 4,000-iteration cap and swapped profile)

Note: at the 15s row, the full parallel suite prints a BUDGET WATCH: screening line for the merge file (42s under -j11, streak 2/5); its quiet solo runs are 9-10.5s.

Build evidence

command: go run ./cmd/docket development test
result: green
head_sha: 0cdf698
ran_at: 2026-10-05T15:22:57Z

Docket-Plan-Path: docs/superpowers/plans/2026-10-05-flaky-tests-track-and-stabilize-intermittent-suite-failures.md
…eports what was stuck

TestObserveRunningThenTerminal no longer discards readManifest's error or
signalGroup's result, and when its unchanged 30s wait expires it reports the
last Observe state/error, group and supervisor liveness, and live.lock state
(describeStuckRun). Mutation-tested: both mutations reddened.
## A1 reproduction (2026-10-05T14:29:24Z)
```
cpu	50	0	0	4	-	4.78
cpu	50	0	0	4	-	7.68
cpu	50	0	0	4	-	10.75
cpu	50	0	0	4	-	10.75
cpu	50	0	0	4	-	13.17
cpu	50	0	0	4	-	14.44
cpu	50	0	0	4	-	16.00
cpu	50	0	0	4	-	17.85
cpu	50	0	0	4	-	17.85
cpu	50	0	0	4	-	19.62
cpu	50	0	0	4	-	21.65
cpu	50	0	0	4	-	22.08
cpu	50	0	0	4	-	23.03
cpu	50	0	0	4	-	23.03
cpu	50	0	0	4	-	24.39
cpu	50	0	0	4	-	25.56
cpu	50	0	0	4	-	27.36
cpu	50	0	0	4	-	27.81
cpu	50	0	0	4	-	27.81
cpu	50	0	0	4	-	27.91
cpu	50	0	0	4	-	28.47
cpu	50	0	0	4	-	28.68
cpu	50	0	0	4	-	30.38
cpu	50	0	0	4	-	30.38
cpu	50	0	0	4	-	30.51
cpu	50	0	0	4	-	31.75
cpu	50	0	0	4	-	31.61
cpu	50	0	0	4	-	32.20
cpu	50	0	0	4	-	32.20
cpu	50	0	0	4	-	34.67
cpu	50	0	0	4	-	35.98
cpu	50	0	0	4	-	37.66
cpu	50	0	0	4	-	39.13
cpu	50	0	0	4	-	39.13
cpu	50	0	0	4	-	38.08
cpu	50	0	0	4	-	45.52
cpu	50	0	0	4	-	49.96
cpu	50	0	0	4	-	52.53
cpu	50	0	0	4	-	53.20
cpu	50	0	0	4	-	53.20
gosuite	50	0	0	4	-	49.32
gosuite	50	0	0	4	-	45.69
gosuite	50	0	0	5	-	42.35
gosuite	50	0	0	4	-	40.56
gosuite	50	0	0	4	-	37.96
gosuite	50	0	0	4	-	37.96
gosuite	50	0	0	3	-	35.16
gosuite	50	0	0	4	-	32.50
gosuite	50	0	0	3	-	32.50
gosuite	50	0	0	4	-	30.22
gosuite	50	0	0	3	-	28.36
gosuite	50	0	0	3	-	26.57
gosuite	50	0	0	4	-	26.57
gosuite	50	0	0	4	-	24.60
gosuite	50	0	0	5	-	22.95
gosuite	50	0	0	4	-	21.51
gosuite	50	0	0	4	-	20.19
gosuite	50	0	0	3	-	20.19
gosuite	50	0	0	4	-	18.81
gosuite	50	0	0	4	-	17.47
gosuite	50	0	0	3	-	16.31
gosuite	50	0	0	4	-	16.31
gosuite	50	0	0	3	-	16.04
gosuite	50	0	0	4	-	14.92
gosuite	50	0	0	4	-	14.92
gosuite	50	0	0	4	-	14.12
gosuite	50	0	0	5	-	13.31
gosuite	50	0	0	3	-	12.49
gosuite	50	0	0	4	-	12.05
gosuite	50	0	0	4	-	12.05
gosuite	50	0	0	3	-	11.40
gosuite	50	0	0	4	-	10.81
gosuite	50	0	0	4	-	10.18
gosuite	50	0	0	3	-	10.18
gosuite	50	0	0	4	-	10.17
gosuite	50	0	0	4	-	9.59
gosuite	50	0	0	4	-	8.99
gosuite	50	0	0	5	-	8.99
gosuite	50	0	0	4	-	8.91
gosuite	50	0	0	4	-	8.83
```
REPRO-TOTAL profile=gosuite iterations=2000 hangs=0 other_failures=0 seconds=154
REPRO-TOTAL profile=cpu iterations=2000 hangs=0 other_failures=0 seconds=160
REPRO-VERDICT not-reproduced
The bounded A1 reproduction (see the previous commit) produced no failure, so
no root-cause fix is claimed. Task 1's diagnostics (no discarded errors,
stuck-run report on timeout) are the A4 outcome; the next sighting will name
what was stuck. The 30s deadline is unchanged.
TestIntegrationFinalizeMerge* now call t.Parallel() (every top-level test,
and every t.Run closure that builds its own fixture; the pure
ConditionAssembly subtests stay as they were under a parallel parent). The
shared fixture takes an injected planning-node builder
(setupRebaseFixtureWithNode / setupMergeFixtureWithNode; the old
setupRebaseFixtureStatus / setupMergeFixture delegate with planningDepsFor, so
every other caller is unchanged) and the shard uses parallelPlanningNode,
which isolates the global-config layer once per process with os.Setenv
instead of t.Setenv (the e2eNode precedent). The new
TestIntegrationFinalizeMergeParallelNodeIsolatesGlobalConfig pins that
isolation; mutation-tested (deleting the os.Setenv turns it red with
'XDG_CONFIG_HOME = "", want the isolated dir ...').

Package-state audit: no test in finalize_merge_integration_test.go assigns a
package-level hook/seam/Fn/Func/Override, calls os/t.Setenv or Chdir, and
neither finalize_merge.go nor the test file declares a package var (before
this change). Traced fixture helpers (setupRebaseFixture chain,
buildConfiguredRepo/newDocketModeRepo, runGit/tryGit, writerAdvance,
carryDroppedCommit, testsupport.TempDir) touch only per-test state, Once-guarded
process config, or the mutex-guarded drain map; the only t.Setenv in the chain
was newGitClient via planningDepsFor, now bypassed. No test stays sequential.
-race run of ^TestIntegrationFinalizeMerge: clean (no DATA RACE).

Quiet solo measurements (shared protocol):
- MEASURE label=B-before file=tests/test_go_integration_app_merge.sh runs=42.3,42.6,44.1 median=42.6 loads=4.72,4.51,4.55 quiet=yes rc=0,0,0 ncpu=11
- MEASURE label=B-after file=tests/test_go_integration_app_merge.sh runs=10.0,10.0,10.5 median=10.0 loads=5.40,6.69,9.38 quiet=yes rc=0,0,0 ncpu=11
Median 42.6s -> 10.0s, under the 25s target; runtime-budgets.tsv row unchanged.
… measurement

No code change: every Part C lever was measured and none produced a win
above run-to-run noise, and the after median already meets the 25s target,
so the fallback (row re-set) does not apply and tests/runtime-budgets.tsv
keeps tests/test_go_finalize_e2e.sh at 30s.

Levers (each measured, each rejected):
- -vet=off on the matrix run: go test's vet subset is cached; measured
  0.5s vs 0.5s warm and 1.86-1.91s vs 1.79-1.80s right after a source edit
  (~0.1s). Not a measurable win; vet line and assert untouched.
- Fake gh build: 0.11-0.27s per fresh temp dir (well under the 2s bar);
  the docket build is cached (0.2-0.6s). Left alone.
- Per-test redundant setup: the critical path is TestE2EStack, a strictly
  sequential chain (two stacked implement() runs at ~6s each through real
  production transactions, then the blocked merge, child merge+closeout and
  root carry); solo it takes 18-21s. No step is test-only or identical to
  another test's work in a shareable way: the other tests each mutate their
  own reachImplemented fixture (claim, worktree, PR state), so sharing would
  need a cloned-snapshot fixture rewriting absolute worktree/run-dir paths,
  and it would only ease contention, not shorten the Stack critical path.

Phase timings before (warm, e2e-phases.sh):
PHASE vet               0.2s rc=0
PHASE testcompile       0.2s rc=0
PHASE docketbuild       0.2s rc=0
PHASE testrun          26.8s rc=0
Slowest tests (all parallel):
--- PASS: TestE2EStack (26.64s)
--- PASS: TestE2EConflictAndRepair (19.31s)
--- PASS: TestE2EResponseLossConvergence (18.24s)
Solo TestE2EStack: 20.8s real (8.8s user, 8.5s sys).

Quiet solo measurements (shared protocol; same tree for both labels, the
4s gap is machine noise - the before runs started at load 8.4-10.2):
- MEASURE label=C-before file=tests/test_go_finalize_e2e.sh runs=27.3,28.2,31.1 median=28.2 loads=9.29,8.40,10.23 quiet=yes rc=0,0,0 ncpu=11
- MEASURE label=C-after file=tests/test_go_finalize_e2e.sh runs=24.1,24.3,25.0 median=24.3 loads=5.62,7.29,9.09 quiet=yes rc=0,0,0 ncpu=11

Completeness-assert mutations: narrowing -run to TestE2EStack turns
"every declared top-level TestE2E test actually ran" NOT OK (rc=1). Renaming
func TestE2EStack to testE2EStackRenamed stays green (rc=0): the rename
drops the function from both the declared and the ran count. That gap is
pre-existing and not widened here; recorded for review.
tests/test_go_race.sh needs no code work; its quiet median puts the earlier
117s (change 0517) and 94s (PR 361) readings on record as contended
measurements, not growth. Its quiet median (44.6s) is under its 60s row.

Measurement note: a first D warm run (54.2s, rc=1) was discarded as a harness
artifact. The caller's TMPDIR ended in "/", so the private TMPDIR became
".../T//m0507.*" and TestNoGitGuardShadowsGitOnPath's string comparison of
the shim path failed in four packages. One cli agent test also failed with
EOF on that run. With TMPDIR normalized (trailing slash stripped), the warm
run and runs 1-3 were green (rc=0).

## A1 reproduction (2026-10-05T14:29:24Z)
```
cpu	50	0	0	4	-	4.78
cpu	50	0	0	4	-	7.68
cpu	50	0	0	4	-	10.75
cpu	50	0	0	4	-	10.75
cpu	50	0	0	4	-	13.17
cpu	50	0	0	4	-	14.44
cpu	50	0	0	4	-	16.00
cpu	50	0	0	4	-	17.85
cpu	50	0	0	4	-	17.85
cpu	50	0	0	4	-	19.62
cpu	50	0	0	4	-	21.65
cpu	50	0	0	4	-	22.08
cpu	50	0	0	4	-	23.03
cpu	50	0	0	4	-	23.03
cpu	50	0	0	4	-	24.39
cpu	50	0	0	4	-	25.56
cpu	50	0	0	4	-	27.36
cpu	50	0	0	4	-	27.81
cpu	50	0	0	4	-	27.81
cpu	50	0	0	4	-	27.91
cpu	50	0	0	4	-	28.47
cpu	50	0	0	4	-	28.68
cpu	50	0	0	4	-	30.38
cpu	50	0	0	4	-	30.38
cpu	50	0	0	4	-	30.51
cpu	50	0	0	4	-	31.75
cpu	50	0	0	4	-	31.61
cpu	50	0	0	4	-	32.20
cpu	50	0	0	4	-	32.20
cpu	50	0	0	4	-	34.67
cpu	50	0	0	4	-	35.98
cpu	50	0	0	4	-	37.66
cpu	50	0	0	4	-	39.13
cpu	50	0	0	4	-	39.13
cpu	50	0	0	4	-	38.08
cpu	50	0	0	4	-	45.52
cpu	50	0	0	4	-	49.96
cpu	50	0	0	4	-	52.53
cpu	50	0	0	4	-	53.20
cpu	50	0	0	4	-	53.20
gosuite	50	0	0	4	-	49.32
gosuite	50	0	0	4	-	45.69
gosuite	50	0	0	5	-	42.35
gosuite	50	0	0	4	-	40.56
gosuite	50	0	0	4	-	37.96
gosuite	50	0	0	4	-	37.96
gosuite	50	0	0	3	-	35.16
gosuite	50	0	0	4	-	32.50
gosuite	50	0	0	3	-	32.50
gosuite	50	0	0	4	-	30.22
gosuite	50	0	0	3	-	28.36
gosuite	50	0	0	3	-	26.57
gosuite	50	0	0	4	-	26.57
gosuite	50	0	0	4	-	24.60
gosuite	50	0	0	5	-	22.95
gosuite	50	0	0	4	-	21.51
gosuite	50	0	0	4	-	20.19
gosuite	50	0	0	3	-	20.19
gosuite	50	0	0	4	-	18.81
gosuite	50	0	0	4	-	17.47
gosuite	50	0	0	3	-	16.31
gosuite	50	0	0	4	-	16.31
gosuite	50	0	0	3	-	16.04
gosuite	50	0	0	4	-	14.92
gosuite	50	0	0	4	-	14.92
gosuite	50	0	0	4	-	14.12
gosuite	50	0	0	5	-	13.31
gosuite	50	0	0	3	-	12.49
gosuite	50	0	0	4	-	12.05
gosuite	50	0	0	4	-	12.05
gosuite	50	0	0	3	-	11.40
gosuite	50	0	0	4	-	10.81
gosuite	50	0	0	4	-	10.18
gosuite	50	0	0	3	-	10.18
gosuite	50	0	0	4	-	10.17
gosuite	50	0	0	4	-	9.59
gosuite	50	0	0	4	-	8.99
gosuite	50	0	0	5	-	8.99
gosuite	50	0	0	4	-	8.91
gosuite	50	0	0	4	-	8.83
```
REPRO-TOTAL profile=gosuite iterations=2000 hangs=0 other_failures=0 seconds=154
REPRO-TOTAL profile=cpu iterations=2000 hangs=0 other_failures=0 seconds=160
REPRO-VERDICT not-reproduced
- MEASURE label=B-before file=tests/test_go_integration_app_merge.sh runs=42.3,42.6,44.1 median=42.6 loads=4.72,4.51,4.55 quiet=yes rc=0,0,0 ncpu=11
- MEASURE label=B-after file=tests/test_go_integration_app_merge.sh runs=10.0,10.0,10.5 median=10.0 loads=5.40,6.69,9.38 quiet=yes rc=0,0,0 ncpu=11
- MEASURE label=C-before file=tests/test_go_finalize_e2e.sh runs=27.3,28.2,31.1 median=28.2 loads=9.29,8.40,10.23 quiet=yes rc=0,0,0 ncpu=11
PHASE vet               0.2s rc=0
PHASE testcompile       0.2s rc=0
PHASE docketbuild       0.2s rc=0
PHASE testrun          26.8s rc=0
Per-test (slowest first):
--- PASS: TestE2EStack (26.64s)
--- PASS: TestE2EConflictAndRepair (19.31s)
--- PASS: TestE2EResponseLossConvergence (18.24s)
--- PASS: TestE2EOrdinaryFinalize (18.24s)
--- PASS: TestE2ENoPathDocketDependency (18.17s)
--- PASS: TestE2EUnsupportedConfigRefused (16.99s)
--- PASS: TestE2EMergeSelectsRebaseShape (16.35s)
--- PASS: TestE2EMergeCommitShape (16.18s)
--- PASS: TestE2EMergeMethodUnavailable (15.87s)
--- PASS: TestE2EOutOfBandMergeRecovered (15.81s)
--- PASS: TestE2ESquashOnlyShape (11.76s)
--- PASS: TestE2EHaltResumeAndReclaim (9.37s)
- MEASURE label=C-after file=tests/test_go_finalize_e2e.sh runs=24.1,24.3,25.0 median=24.3 loads=5.62,7.29,9.09 quiet=yes rc=0,0,0 ncpu=11
- MEASURE label=D file=tests/test_go_race.sh runs=44.1,44.6,45.0 median=44.6 loads=4.23,3.65,3.48 quiet=yes rc=0,0,0 ncpu=11
…nding 3)

Quiet solo median after parallelization is 10.0s (10.0/10.0/10.5);
rounding rule gives 15s, not 30s.
@danielhanold
danielhanold merged commit b8f3214 into main Oct 5, 2026
7 checks passed
@danielhanold
danielhanold deleted the fix/flaky-tests-track-and-stabilize-intermittent-suite-failures branch October 5, 2026 16:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant