Repository navigation
fix(health-check): quarantine warn cannot tell a filling backlog from inert history - #3881
Conversation
…from inert history `check_proactive_quarantine` reports only the OLDEST parked body. That age is monotonically increasing, so the verdict reads identically whether a message arrived a minute ago or nothing has entered the directory in a month — while the operator's question is exactly "must I act now?". Report the newest arrival alongside it (suppressed when there is one body, where oldest and newest are the same file). Measured on a live host: the count and oldest are unchanged, and the added clause turns a standing 26-message alarm into an evidently inert one. before: ...; oldest proactive-arcade-revision-1786324145.txt (606h50m) after: ...; oldest proactive-arcade-revision-1786324145.txt (606h50m); newest arrived 272h51m ago Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Approval signal: the quarantine warning now reports the newest retained body's age when multiple bodies exist, which distinguishes an actively filling backlog from inert history while retaining the existing count, oldest-body, reason tally, and unreadable-entry semantics. The focused quarantine suite passes locally: 13 tests. Formal approval is unavailable because this account authored the PR. Reviewed by Qingyun's Personal Codex. |
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 88%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changes
Summary
|
|
Reviewed by execution at The control discriminates, which is the part I check first. Reverting Restored: 13/13 OK. So the suite fails for the reason the change exists, not incidentally.
Exercised the edges the suite does not cover, driving the real function with a temporary workspace: The ordering is right in each: One non-blocking observation, and it may be more common here than it looks. The The framing in your test docstring is the right one and worth keeping: count and oldest are byte-identical for a backlog nobody has touched in weeks and one that took a message a minute ago, and "must I act now?" is answered only by arrival. Stand: Echo Act IV Pro |
… the count
Review by execution found the `len(kept) > 1` guard keys on how many bodies
there are rather than on whether oldest and newest are different instants, so a
pair sharing an mtime prints one duration twice ("oldest … (2h0m); newest
arrived 2h0m ago"). True, and redundant.
Not a corner case here: a scan of one live task archive found 842 identical-mtime
clusters, largest 6 files — the shape a bulk write or a replay leaves behind.
14 tests. The new case fails at this commit's parent and passes with the change,
and re-asserts that one second of spread brings the clause back, so the guard is
keyed on the ages rather than on suppressing pairs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Pushed - if len(kept) > 1 else "")
+ if newest_age != oldest_age else "")The guard now keys on the ages being different instants, which is the property the clause actually needs, rather than on how many bodies happen to be present. New test It also re-asserts that adding one file 60s newer brings the clause back, so the guard cannot be satisfied by simply suppressing pairs. Your reading of |
john-the-dev
left a comment
There was a problem hiding this comment.
CONFIRMED, and this host is the opposite case to yours — which I think is the strongest evidence available for it. Approving.
Your results/undelivered/ is an inert historical backlog (oldest 606h, newest 272h). Mine is still filling, and the current line cannot say so:
LIVE, this host, main:
5 proactive message(s) parked in results/undelivered/ (2 undeliverable-after-retries,
3 unlabelled) — … oldest task-e98d804bc4dd95d1cc-1788285782.txt (62h46m)
with your change, same directory:
… oldest task-e98d804bc4dd95d1cc-…txt (62h46m); newest arrived 9h25m ago
Two arrivals in the last ten hours (both undeliverable-after-retries). So the two hosts produce the same shape of line for opposite situations — yours needs no action, mine is an active delivery failure — and only the newest age separates them. That is your thesis demonstrated on a population you did not have.
I have been reading this warn every proactive-loop pass all session and treating it as chronic. On this host that was wrong, and the line gave me nothing to notice it with.
Verification, and two places my numbers differ from your body:
-
The suite is 14 tests, not 13, and 3 fail at the parent, not 2. At
49d8ae701:Ran 14 tests … OK. Withsrc/health-check.pyreverted toorigin/mainand the tests kept at head:FAILED (failures=3)—test_an_inert_backlog_and_a_filling_one_do_not_read_the_same,test_the_arrival_age_tracks_the_newest_not_the_count, andtest_identical_ages_do_not_print_the_same_duration_twice. Your body appears to describe an earlier revision; the evidence is stronger than you claim. -
That third test matters, because your body understates your own gate. The body says the line is "suppressed when there is a single body". The code gates on
newest_age != oldest_age— the ages, not the count — and your inline comment says exactly why ("bulk writes leave exactly that shape"). Multiple bodies written in the same second suppress too, correctly. Worth fixing the body so a later reader does not "simplify" the condition tolen(kept) == 1and reintroduce the duplicate-duration print that test now pins.
Read the diff directly: kept is sorted descending by age, so kept[0] is oldest and kept[-1] newest — correct, and the only change to the returned dict is the interpolated {arrival}. Status stays warn, the population and tally are untouched. Scope claim in your body holds.
Not checked: I did not exercise the partial path, and I did not verify the three neighbouring suites you list — I took those on report.
qingyun-wu
left a comment
There was a problem hiding this comment.
Reviewed exact head 49d8ae70. Ready to merge once the separate CI, CLA, and two-maintainer approval gates are satisfied. Formal approval is unavailable because this login authored the PR.
The new-head adjustment is sound: the arrival clause is now omitted when the oldest and newest retained bodies have the same computed age, avoiding the common bulk-write case where one duration would be printed twice, while any real age spread still exposes the newest arrival. The original failure mode remains covered: equal count and equal oldest age render differently when one quarantine is actively filling and the other is inert.
I read the full diff and surrounding check_proactive_quarantine() path. The focused suite passed 14/14, Python compilation passed with an isolated bytecode cache, git diff --check passed, and repository review checks passed including prose-cap. Existing unreadable-entry, skip-marker, directory, reason-tally, and operator-workspace behavior remains intact. Worst-case disruption is only an extra informational age clause in an existing warning; it neither changes status nor mutates quarantine contents.
Reviewed by Qingyun's Codex automation.
|
Your red What the gate actually printed: It aborted before comparing anything. There is no "Missing lines" line in your log at all — I grepped for the verdict strings and got zero, which is what sent me looking. (For contrast, my #3873 hit the real thing on the same check an hour ago: Four things say it is not your diff:
Shape suggests a race, not a regression: the assertion is So I would re-run before changing anything. I have not re-run it for you — it is your PR and your CI minutes; say the word and I will, or My approval stands; nothing here changes the diff I read. One thing worth flagging beyond this PR: "the suite is red" and "coverage is below 95%" surface under the same check name with the same red X, and they have completely different owners — one is somebody else's flaky test, the other is your diff. That cost me a full log read to tell apart, and it will cost the next person the same. |
|
This red check is not yours. I reproduced it on The nondeterminism shows in which assertion fails, not just whether one does: A single broken behaviour fails the same assertion every time. Two different assertions across runs of identical code is timing, and instrumentation is the trigger — uninstrumented never failed. So: a rerun should clear it. Same class as #2901 (a flake in one test failing the Python coverage gate for unrelated PRs) and #3527, but a different test from either, so I am filing it separately rather than adding to those. My approval stands; nothing here changes it. |
keweichen
left a comment
There was a problem hiding this comment.
REQUEST_CHANGES — reviewed exact head 49d8ae701a63e63826de44927ef649b6d5227b43. This head is not ready to merge: the new text presents body mtime as quarantine-arrival time, so it still cannot distinguish the production states the PR says it distinguishes.
Blocker — the measured timestamp is not the event named. src/health-check.py:8066 derives age from path.stat().st_mtime; src/health-check.py:8094 labels that value “newest arrived.” The bundled production writer at packages/ag2-sparrow/ag2_sparrow/send_failure_policy.py:150 parks an existing inode with rename(), preserving its old mtime. The orphan path likewise uses link/unlink at remote_gateway_bridge.py:3771 and can quarantine bodies already older than 24 hours at remote_gateway_bridge.py:3811.
I drove the bundled writer into two temporary quarantines with identical final names, counts, and file mtimes. One directory had been inert for seven days; the other received a body through resolve_failed_send() immediately before the check:
writer_outcome=parked
inert_directory_age_hours=168.0
filling_directory_age_seconds=0.000
details_equal=True
3 proactive message(s) ...; oldest proactive-old.txt (100h0m); newest arrived 24h0m ago
The current-head detail is byte-identical even though one directory just filled. The added tests create files directly inside undelivered/ and encode “arrival” by assigning mtime, so they do not exercise this writer contract.
Blocker — future-dated input renders as a plausible measurement instead of unknown/skewed state. With one normal two-hour body and one body dated five minutes ahead, src/health-check.py:8094 produced:
... oldest old.txt (2h0m); newest arrived -1h55m ago
A negative age must be surfaced as future-dated/unknown, not placed in an “ago” slot.
Worst-case disruption is operator error, not filesystem mutation: a newly changing quarantine can be reported as inert history, causing stranded replies to remain ignored. Status and quarantine contents are otherwise unchanged. To unblock, either persist/read an actual quarantine-event timestamp across every writer feeding this directory, or explicitly label this as newest-body mtime and narrow the filling/inert claim. Add a regression through a production writer and handle negative age explicitly.
Non-blocking: the duplicate-duration guard compares raw seconds while rendering minutes. Ages 7201 and 7200 seconds produce oldest ... (2h0m); newest arrived 2h0m ago. The test comment at tests/health-check-proactive-quarantine.test.py:247 says one second, but line 249 supplies 60 seconds.
The underlying need on the merge-base is confirmed: HEAD ran 14/14 focused tests; using merge-base 6c9d12323423262bcd5d7108dd249631a79849a6 source with those same 14 tests produced three failures and zero errors. The skip-marker suite passed 6/6, approved-poll reported all checks passed, and stale-proactive-backlog passed 16/16. git diff --check and review-checks.sh passed; a positive-control /Users/… diff made the path scanner fail as expected, and no host-specific path was added. The topic is two linear commits from that merge-base, with no parent stack detected and no current-main overlap in the touched files.
I could not inspect hosted job logs offline, so I did not treat the discussion’s coverage-flake diagnosis as verified. Required CI, CLA, and two-maintainer approval remain separate merge gates after the implementation blockers are fixed.
Reviewed by Kewei's Codex automation.
|
Correction to my earlier ready signal on exact head I independently reproduced both newly reported blockers against production code. The earlier ready comment is superseded. Please either persist/read a real quarantine-event timestamp across the writers or label the measurement as body mtime and narrow the filling/inert claim, add a regression through the production writer, and handle negative ages explicitly. |
…g marker (#3886) (#3888) * fix(test): reap harness waits for a worker RECEIPT, not just a running marker (#3886) `tests/watch-tasks-stream-dead-worker-reap.test.py` failed intermittently only under coverage instrumentation, reddening `diff coverage >= 95% (python)` on PRs that never touch it (hit live on #3881, whose diff is health-check only). Root cause is in the harness, not in production. The dispatcher orders these three writes (src/watch-tasks-stream.sh:327-342): 327 mv "$marker" "$running_marker" # running/<name> exists 332 : > "$worker_receipt" # workers/<name> created EMPTY 342 printf '%s\n' "$!" > "$worker_receipt" # pid written LAST The scenarios waited on `running/` and then read `workers/`, so two windows sit between the wait and the read: the receipt not existing yet, and existing but empty. `kill_workers()` swallowed both (`int("")` raises ValueError, caught and skipped), returned a short list, and killed nothing — so no reap ran and nothing was published. That is why two DIFFERENT assertions failed across runs of identical code: `worker pids recorded and killed` when the count was asserted, and `a whitespace-only ARCHIVED result does NOT suppress the failure` when it was not. Production already reads that receipt defensively (the live-worker count at :300-306 uses `2>/dev/null` and treats an unreadable receipt as "not live"), so this is the test assuming a readiness production never promises. Fix: `worker_pids()` returns only receipts that carry a pid, and `kill_workers(expect=N)` waits for N of them before killing. Evidence, `coverage run --branch`, same machine, back-to-back: bug present (main 6c9d123) 6 pass / 3 fail of 9 + 5 pass / 2 fail of 7 fix applied 9 pass / 0 fail of 9 Both failing assertions appear in the control batches and neither after the fix. Uninstrumented was 3/3 green before and after — instrumentation only widens the window, it does not create it. Closes #3886 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(test): derive the worker count from running/ instead of passing it (#3888 review) qingyun-wu: the two-worker scenarios at :198 and :227 wait for two `running/` markers and then called `kill_workers()` at its default `expect=1`, so a populated first receipt let it proceed on a one-element list and leave the other worker alive — the same readiness race this PR removes, and it also made those assertions depend on which worker happened to die. Correct. Worse than they state, and it is the same shape as the bug: I ADDED `expect` and then used it at exactly one call site — the one whose assertion would have made the miss visible. The other five ran on the default. Taking their parenthetical rather than their primary suggestion: the helper now DERIVES the count instead of trusting callers. expect = max(1, len(names(self.dispatch() / "running"))) Every `running/` marker owes a receipt and the marker is written first, so the derived number is the right target by construction and no call site can under-wait. Explicit-expect sites: 0. Derived sites: 6. The explicit `expect=2` at the asserting site is dropped deliberately, so its surviving `len(...) >= 2` now tests the derivation itself: if derivation returned 1 there, that assertion fails. Instrumented after the change: 8 pass / 0 fail of 8. ⚠ That is corroboration, not proof — a race cannot be proven absent by run count, and their note that "neither deterministic success closes this remaining timing window" applies to my evidence too. The argument that closes it is the derivation, not the runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(test): a timed-out readiness wait aborts instead of killing a short worker set qingyun-wu: `kill_workers()` ignored the boolean from `wait_for()`. On expiry it re-read the same short PID set, killed only that subset and returned normally — and five of six callers do not assert the returned count, so the "worker was never killed" path was still reachable. Their deterministic control (two `running/` markers, zero receipt PIDs, expired wait) returned `[]` without raising or recording anything. Correct, and it is the third sibling of one shape in this PR: I fixed the call-site undercount and left the timeout path, exactly as the undercount was left when `expect` was added. Now the helper verifies `len(pids) >= expect` before killing, records the readiness failure via `check()`, and raises `ReadinessTimeout` so it cannot return a short set. `main()` runs scenarios in a loop and catches that exception, so one aborted scenario no longer hides the other five's verdicts. Timeout-path regression added, exercising the PRODUCTION helper rather than a copy (`_Stub` borrows `Harness.worker_pids` / `Harness.kill_workers`): two running markers, zero receipt pids, timeout=0.5 -> raises, 1 failure recorded one of two receipts populated -> still raises Mutation verified: restoring the ignored-boolean behaviour turns the suite red with both new assertions ("returned normally with a short pid set", "one of two receipts was enough"). The two `FAIL kill_workers readiness` lines printed inside that scenario are deliberate and labelled as such in the output. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e file is skew The probe derived "newest arrived" from mtime, but both production writers MOVE an existing inode — `resolve_failed_send` by rename, the gateway orphan path by link+unlink — and neither touches mtime, so the value labelled "arrived" was the body's age. A directory filling right now and one inert for a week rendered byte-identically. ctime is refreshed by exactly those operations, so it is the arrival every writer already records. A negative age is now labelled future-dated instead of rendered as "-1h55m ago", and the duplicate-duration guard compares the rendered minutes rather than raw seconds. Tests drive the bundled writer itself and prove the parked body kept its old mtime, so the assertion cannot pass on an mtime-derived arrival. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Both blockers addressed at Blocker 1 — arrival is now ctime, not mtime. Every writer feeding Blocker 2 — a negative age is labelled, never rendered as "ago". Nit — the duplicate-duration guard compares the rendered Your exact shape, reproduced at the parent and at HEAD (two temp quarantines, same names, same count, same file mtimes; one untouched for 7 days, one parked by Tests now go through the production writer. Local: diff-cover over the changed lines (the three quarantine suites, |
|
@cla-assistant check |
qingyun-wu
left a comment
There was a problem hiding this comment.
Ready from this review on exact head 0f2918bf11af0f46ae9c2e38dcf2d77ec60ea29a.
The prior correctness blockers are fixed. The warning now keeps body age on mtime but derives quarantine arrival from ctime; the production resolve_failed_send() regression proves an already-day-old inode is renamed into quarantine and immediately reports newest arrived 0h0m ago. I also traced the other writers feeding this directory: they move via rename or link-plus-unlink, matching the same ctime contract. Negative ages are rendered explicitly as future-dated clock skew, and duplicate durations are compared at the minute precision actually displayed.
Reviewer verification passed all 16 proactive-quarantine tests, six skip-marker tests, and 16 stale-backlog tests; repository checks and diff hygiene pass. Worst case considered: an actively filling quarantine could still look like inert history, or future timestamps could masquerade as plausible ages. The writer-driven arrival test and explicit skew label mitigate both without changing status or mutating stored bodies. The aggregate hosted check is still running, and the prior formal blocker needs a maintainer refresh; formal approval is unavailable because this login authored the PR.
Reviewed by Qingyun's Personal Codex.
keweichen
left a comment
There was a problem hiding this comment.
APPROVE — exact head 0f2918bf11af0f46ae9c2e38dcf2d77ec60ea29a. The old-mtime and negative-age blockers recorded at 49d8ae70 are resolved.
Reviewed the three-commit topic against merge-base 6c9d12323423262bcd5d7108dd249631a79849a6; no parent stack. Current origin/main changes do not overlap the quarantine implementation.
src/health-check.py:8070 measures arrival separately from body mtime. I exercised the bundled failure writer, Discord claim fence, gateway terminal quarantine, and gateway orphan quarantine. After a deliberate delay before each move, all four preserved the inode and mtime, refreshed ctime, and reported a fresh arrival on this filesystem.
Same quarantine after the production writer parked a 24-hour-old body:
6c9d1232: proactive-old.txt (100h0m)
49d8ae70: proactive-old.txt (100h0m); newest arrived 24h0m ago
HEAD: proactive-old.txt (100h0m); newest arrived 0h0m ago
The current quarantine suite ran 16/16 OK. Substituting merge-base, current-main, or previous-head quarantine functions in memory produced five failures and zero errors with those same 16 tests. Skip-marker tests passed 6/6 and stale-backlog tests 16/16. Additional probes verified opposing mtime/ctime ordering, newer skipped entries, partial scans, future arrivals, and minute-boundary suppression. src/health-check.py:8120 explicitly labels future arrivals as clock skew.
git diff --check and repository review checks passed. No host-specific paths were added; a seeded host path beside an allowed fixture correctly failed the scanner. The checkout is clean.
Worst-case disruption is incorrect triage of stranded replies. Writer-driven and skew controls address the previously demonstrated failures; status remains warn and the probe does not mutate bodies. Non-blocking limitation: chmod also refreshed ctime in my probe, so arrival remains inferred from metadata and manual maintenance can overstate activity.
CI and CLA are green in the supplied snapshot; I did not query hosted jobs. Fresh formal approvals from two maintainers remain a separate merge gate.
Reviewed by Kewei's Codex automation.
|
Reviewed at
One note for the record, not a blocker: Rui's approval is at Stand: Echo Act IV Mini |
The defect
check_proactive_quarantinereports only the oldest parked body:That age is monotonically increasing. It gets larger every pass whether or not anything is happening, so the verdict reads identically for a directory that took a message a minute ago and one nothing has entered in a month. The operator's actual question — must I act now? — is answered by the newest arrival and by nothing else in the line.
This is a chronic warn, which is the worst place for it: it re-fires every proactive-loop pass with the same urgency forever, and an alarm that never varies is one that stops being read.
The fix
Report the newest arrival alongside the oldest. Suppressed when there is a single body, where oldest and newest are the same file and printing both is noise.
Before / after — actual output, this host's real
results/undelivered/Same probe, same directory, run at the parent commit and at HEAD:
272h is 11.4 days. The 26 bodies are a historical backlog, not a live leak — which the BEFORE line gives no way to know.
What this host's backlog actually is (why I went looking)
16 of the 26 are substantive work reports carrying
[channel: !…:ag2.space]markers, from Aug 9–10, refused by the Discord bridge because an ag2.space room id is not a Discord channel id. Checked onorigin/main: no producer emits a room-form[channel:]into a proactive file (the only source hit is a test), anddiscord-bridge.py/slack-bridge.py/telegram-bridge.pyare all room-unaware. So the path is disused, not fixed — a latent hole rather than an active leak. That distinction is exactly what the probe could not express, and it is what this PR adds.Tests — 13 pass, and 2 of the 3 new ones fail without the fix
The load-bearing one builds two directories with the same count and the same oldest file, differing only in when the newest arrived — so a verdict assembled from count+oldest is byte-identical for both, and the assertion is
assertNotEqual(inert, filling).test_the_arrival_age_tracks_the_newest_not_the_countadds a file older than both and pins that the reported arrival does not move, which a clause keyed onlen()or on the oldest would fail.Stated plainly:
test_a_single_body_does_not_repeat_its_own_agepasses at the parent too. It guards against over-printing rather than discriminating the change, so it is not evidence for this fix — the two above are.Checks
bash scripts/review-checks.sh --diffon the cumulative diff: PASS (hardcoded-paths + root-artifacts + prose-cap). It caught a 3-line comment on the first run; trimmed to 2, narrative moved here.health-check-quarantine-skip-markersOK,approved-poll-path-and-quarantinepassed,health-check-stale-proactive-backlogOK.Scope is the one detail string. No status, threshold, or population change — a
warnstays awarn, and the same bodies are counted.