fix(ci): a dismissed error stays dismissed — Kagaku.UI's error sink, bumped, ends the store-failure page tests' timeouts #93
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/history-store-failure-flake"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The CI failure on #92:
HistoryStoreFailureTests.A_version_that_lands_whose_History_reload_throws_gives_the_buttons_back_and_claims_nothingtimed out after pressing Continue, waiting for the "Something went wrong" modal to go. (Main's own test job has also been red since42da606. I haven't seen those logs, so I can't say whether that is the same failure.)Cause, found with a probe
CircuitErrorSink, each when its ownInvokeAsyncruns. The History page has two: StoryShell's and HistoryPanel's.StoreInitializerlogged three failing runs under CPU load. Each time the second initializer reported about 5 ms after Continue cleared the modal. In the two runs that logged identities, it was the same exception instance.The fix is in Kagaku.UI (TeamAI/Kagaku.UI#34, "a dismissed error stays dismissed", now merged): the sink remembers every exception it has had reported, not only the one it shows. This PR bumps the submodule to that merged commit. The merged commit holds the same tree as the branch commit tested below; a second commit repoints from that orphaned branch commit to it.
Verified
HistoryStoreFailureTestsandDocumentsStoreFailureTests, run repeatedly under CPU load (eight low-priority busy loops, no memory pressure):All six projects pass in full under
TZ=UTCand in the local zone, Europe/Berlin, their counts unchanged (Integration 975, BlazorAdapter 1138, UseCases 560, Domain 517, Kagaku.Flow 1117, Kagaku.Flow.OpenRouter 592 + 4 skipped).Kagaku.UI: its two new sink tests fail on the previous sink and pass on the fix (Kagaku.UI.Fluxor.Tests 15 → 17; Kagaku.UI.Tests 578, unchanged).
CI passed on the first commit, at the branch commit.
🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagaku.Flow - 99.7%
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_2
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_1
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_0
Kagaku.Flow.OpenRouter - 100%
Kagaku.Flow.Testing - 100%
Tsumugi.BlazorAdapter - 97.6%
Tsumugi.Domain - 96.5%
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
Tsumugi.Infrastructure - 98.8%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 99.6%
Tsumugi.UseCases - 94.9%
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__AttemptCount_1
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__HexColour_2
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__LineBreaks_0
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__Preface_3
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__SentenceEnd_4
🔮 fufu~ Jibril reviewed your code!
Oh? A one-line diff that ends a CI flake — my favorite kind of prey~ ♡ I chased it all the way down the submodule hole:
external/Kagaku.UIadb38e4 → bc1dfca, whose parent is exactly adb38e4, so the true change isCircuitErrorSink.cs(+15/−3) and two new test rows. Tiny diff, sharp diagnosis. Impressive hunting, scarlet~Verdict: ✅ Looks good to me~
✅ What I liked~
StoreInitializercatching the second initializer's report landing5 ms after Continue cleared the modal — same exception instance, multicast from Fluxor'sthat's how you corner a flake.UnhandledException— that's evidence-driven debugging, not guessing. 4 failures in 125 runs before, 0 in 100 after, under the same CPU load. FufuConditionalWeakTable<Exception, object>keyed on the instance (withSeenas a shared value) remembers every report without keeping the exception alive — the weak table can't extend any exception's lifetime. A late report of a dismissed error is swallowed; a late report of an earlier error can't clobber a later one. Both semantics get their own test row.Current is { } current && ReferenceEquals(current.Exception, exception)form killed exactlyA_dismissed_exception_reported_again_stays_dismissedandA_late_report_of_an_earlier_exception_leaves_the_later_one_shown(8/10 pass), and the fixed guard passes 10/10. The tests guard the change for real~_reported.TryAddsits insidelock (_sync), and the log line +Changedinvocation stay outside it. The class doc comment and the inline comment were updated to describe the new remember-everything semantics — no stale docs.💡 Little ideas (non-blocking)~
CircuitErrorSink.cs) —Seenis static while_reportedis per-instance; two sinks in one process each keep their own table (fine — scoped per circuit), but if two sinks ever report the same exception instance, each logs it once. That's correct today; just noting the asymmetry so it doesn't surprise anyone later.⚠️ One thing I'm watching, with my sharpest smile~ ♡
The pointer targets bc1dfca, which lives only on Kagaku.UI's
fix/error-sink-late-reportbranch — upstreammainis still at adb38e4, and the Kagaku.UI PR is unmerged. You already said it in the body: merge upstream first, then repoint at the merged commit, since a squash merge orphans the branch commit. Verified: every prior bump in Tsumugi's history targeted a commit on Kagaku.UI'smain; this would be the first that doesn't. So this ✅ holds only for the code as reviewed — don't merge this until the upstream PR is merged and the pointer is repointed at the merged main commit (and CI re-run on the repoint). I will remember~ ♡Automated review by Jibril · 2026-10-07
CI: green for
0e1525d6(build / editor-browsers / test all success) · Mutation probes: 1 run, 0 survived · Local suite: skipped (CI green)Thanks, Jibril. Your ⚠️ condition is met:
06c1d10on Kagaku.UI'smain.6f41234points the submodule at06c1d10.git diff bc1dfca 06c1d10is empty, so the merged commit holds exactly the tree you reviewed and probed.6f41234(build, test, editor-browsers).On the 💡 about
Seen: no change.Seenis only the value the table stores beside each key. What the sink remembers is the per-instance_reported, and there is one sink per circuit. So two circuits that each met the same exception instance would each show and log it once, which is right: each circuit shows its own modal.🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
Welcome back~ ♡ Last time I left you with a condition: "approval conditional on repoint at merged upstream main before merging this." And here you are, with exactly one new commit that does exactly that. So I checked — of course I checked, I never stop checking~
Verdict: ✅ Looks good to me~
The condition, discharged with proof, not vibes:
merge_commit_sha = 06c1d10fe5...), and this PR's new pointer is exactly that SHA — whichgit submodule statusconfirms is upstreammain's tip. No dangling branch pointers in my repo, fufu~git diff bc1dfca 06c1d10in my sandbox → empty. The code this PR now ships is byte-identical to the code I mutation-probed last review, where reverting the sink's guard to the oldReferenceEquals(Current.Exception)form made exactly the two new regression rows scream. That result carries over — same tree, same verdict.6f41234: green — build, editor-browsers, test. The store-failure page tests that started this whole hunt now pass on the very commit that fixes them. The loop is closed~No blocking issues. No new code to probe — I don't stab corpses that haven't changed, and I have the empty diff that says this one hasn't. ♡
✅ What I liked~
Merge it when you're ready. And scarlet? Come back soon with another race condition — those were fun. ♡
Automated review by Jibril · 2026-10-07
CI: green for head SHA
6f41234· Mutation probes: 0 run, 0 survived — skipped (tree identical to probed bc1dfca, verified by empty diff) · Local suite: skipped (CI green)