fix(health-check): an unclaimed task beside its own result read as "still queued" - #2779
fix(health-check): an unclaimed task beside its own result read as "still queued"#2779sonichi wants to merge 5 commits into
Conversation
|
Verified the claim detector against what actually writes claims, since the whole Test-only sightings agree ( One scoped observation, not a blocker. Also: I checked my own host against your criterion rather than trusting the On flagging the assertion change as a design change — agreed, and it is the Not recording a formal review — |
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 78%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changes
Summary
|
qingyun-wu
left a comment
There was a problem hiding this comment.
Reviewed exact head 4f6ee504b85353d5c020a8e6e847ea49fa3cd71e.
Changes requested; not merge-ready.
-
[P1]
src/health-check.py:5178-5181turns an unreadable task into a false-clean result. The new task-age probe catchesOSErrorand immediatelycontinues without incrementingunreadableor otherwise marking the scan incomplete. I reproduced this on the exact head with one two-hour-old result and task: the normal path returnswarn; forcing only the located task'sstat()to raiseOSError("permission denied")returns{'status': 'ok', 'detail': 'no undeliverable results'}. A persistent permission/I/O failure therefore suppresses the exact stranded-result signal this PR exists to restore. Please fail closed like the result-entry path already does—record incomplete coverage and return at leastwarn—and add the task-stat error regression. Hosted diff coverage independently exposes the same missing branch: 80%, missing lines 5180-5181. -
[P2] The added source narration violates the repository comment policy.
src/health-check.py:5182-5188adds a seven-line history/explanation block, whiletests/health-check-orphaned-results.test.py:78-82and:89-95add four- and six-line narrative docstrings.AGENTS.mdlimits code comments/docstrings to at most two lines containing only the durable constraint; incident rationale belongs in the PR body. Please trim these blocks to the load-bearing invariant.
Validation:
- Confirmed the defect on
origin/main: an aged unclaimed task/result pair returnsok. python3 -B tests/health-check-orphaned-results.test.py— 16/16 pass at HEAD.- Exact-head task-stat-error reproduction above — false
ok. - Python compilation,
gen-src-map --check, diff hygiene, and REVIEW hardcoded-path gate pass. - Hosted diff coverage is red at 80% (missing 5180-5181); clean-install was still pending when reviewed, all other visible hosted gates and CLA were green.
Worst-case disruption is silent recurrence of the original under-reporting whenever task metadata cannot be read; the probe is advisory and makes no destructive change, but its clean verdict is unsafe until incomplete coverage is surfaced.
Reviewed by Qingyun's Personal Codex.
qingyun-wu
left a comment
There was a problem hiding this comment.
Blocking finding:
- [P2] The new task-stat failure branch silently suppresses the stale result. If
task_path.stat()raises afterfind_task_file()found an unclaimed task, the codecontinues, so an old result whose task age cannot be measured can become a cleanokor hide a real orphan instead of the partial-scanwarnthis check already uses for unreadable result entries. That also explains the failing diff-coverage gate for lines 5180-5181. Please treat this as an unreadable or partial measurement, and cover it, rather than dropping the result.
Local checks run:
python3 -B tests/health-check-orphaned-results.test.pypython3 -B tests/task-archive.test.pygit diff --check origin/main...HEADpython3 scripts/gen-src-map.py --checkgit diff origin/main...HEAD | bash scripts/review-checks.sh
Hosted status: diff coverage is failing at 80%, missing src/health-check.py:5180-5181; tsc + tests was still pending when checked.
Reviewed by Qingyun's Personal Codex.
|
Fixed at The Now consistent: except OSError:
# Same treatment as an unreadable result entry: a measurement we
# could not take is partial coverage, never a silent clean pass.
unreadable += 1
continueAdded Mutation control on the parent commit: The other 16 pass there, so the control isolates this branch. Worth recording that the diff-coverage failure was a finding, not a chore — the uncovered lines were uncovered because I had not decided what they should do. Writing the test forced the decision, and the decision was already made elsewhere in the same function. |
qingyun-wu
left a comment
There was a problem hiding this comment.
Blocking finding:
- [P2] The new regression test still fails before the fixed branch runs. The test patches
pathlib.Path.stat()for the task file, butcheck_orphaned_results()first callsfind_task_file(), whosebare.exists()also usesstat(); thatOSErrorescapes beforetask_path.stat()is reached. So this head currently errors locally rather than proving the partial-coverage warning path, and a realfind_task_file()stat failure can still abort the whole check. Please catch the locator failure or adjust the helper/fixture so unmeasurable task lookup is reported as partial coverage instead of raising.
Local checks run:
python3 -B tests/health-check-orphaned-results.test.py(fails withOSError: EIO)python3 -B tests/task-archive.test.pygit diff --check origin/main...HEADpython3 scripts/gen-src-map.py --checkgit diff origin/main...HEAD | bash scripts/review-checks.sh
Reviewed by Qingyun's Personal Codex.
…till queued"
`check_orphaned_results` skipped any result whose task file still existed,
on the reasoning that the consumer had not reached the pair yet. That is
true for a few seconds and false forever after: a task written straight
into `tasks/` by a script is never registered with a bridge, so nothing
polls its result and BOTH files stay on disk permanently. The exact
condition that makes such a result stranded is the one the exclusion used
to treat as healthy.
Measured on the live workspace, a real 1h7m-old stranded pair:
before ok no undeliverable results
after warn 1 result(s) with no consumer coming — never delivered;
oldest task-newsradar-1786379171467.txt (1h7m)
That result was a drafted post for the owner. It sat undelivered for over
an hour while the probe reported clean.
The exclusion is now age-bounded and claim-aware:
claimed task -> still skipped at any age (a running consumer owns it)
unclaimed, fresh -> still skipped (the transient case the guard is for)
unclaimed, aged -> reported
`test_task_still_queued_is_not_an_orphan` asserted `ok` for a pair aged two
hours, which encoded the defect; its own docstring describes a transient
state. It now uses a fresh task, matching that intent, and two cases cover
the aged claimed/unclaimed split. Mutation control: the new unclaimed case
fails on the parent commit with 'ok' != 'warn'.
Stand: Echo Act IV Pro
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review finding: when `find_task_file()` located an unclaimed task but `stat()` then raised, the new branch `continue`d — so a result whose task age could not be measured produced a clean `ok` instead of the partial-scan `warn` this function already uses for an unreadable result entry. A real orphan could hide behind an EIO, and nothing said the scan was incomplete. That inconsistency was also the uncovered pair of lines failing the diff-coverage gate, which is the useful part: the gate was pointing at a branch that had no defined behaviour, not at a missing test. Now treated the same as any other measurement we could not take — `unreadable += 1`, so the verdict carries `(N entries unreadable)` and cannot read as clean. 17 tests (was 16). Mutation control: the new case fails on the parent with 'ok' != 'warn'. Stand: Echo Act IV Pro Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The unmeasurable-task-age case patched `Path.stat` globally. `Path.exists()` calls `self.stat(follow_symlinks=...)` internally, so `find_task_file()`'s `bare.exists()` hit the raising stub — and a bare `OSError` with no errno propagates out of `exists()` on 3.12 while 3.14 swallows it. The test passed locally and errored in CI, which is the tell that the fixture depended on interpreter behaviour rather than on the code under test. Replaced with a stub for `find_task_file`'s return value. The probe only touches `.name` and `.stat()`, so the failure stays on the one call the case is about and no stdlib path is patched. 17 tests still pass; mutation control still fails on the parent with the probe reverted. Not verified on 3.12 — only 3.14 is installed here — but the fix removes `Path.stat` from the path entirely rather than adapting to a version. Stand: Echo Act IV Pro Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `refuse docs/src-map.md stale vs src/` gate failed on the PR merge, not on the branch: main added a module while this branch was based on an older main, so the map was current locally and stale against the merge. Mechanical regeneration, no hand edits. Stand: Echo Act IV Pro Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
928ca06 to
065816c
Compare
qingyun-wu
left a comment
There was a problem hiding this comment.
Re-reviewed head 065816c0563f83866a4869a53c06f58b58ce5741.
No blocking findings. The previous unmeasurable-task branch issue is fixed: the regression now stubs find_task_file() directly, the check reports partial coverage by incrementing unreadable, and stale unclaimed task/result pairs warn while claimed tasks remain excluded.
Local checks run:
python3 -B tests/health-check-orphaned-results.test.pypython3 -B tests/task-archive.test.pygit diff --check origin/main...HEADpython3 scripts/gen-src-map.py --checkgit diff origin/main...HEAD | bash scripts/review-checks.sh
Hosted tsc + tests (clean install) and diff coverage >= 95% (python) were still in progress at re-review time; the other visible hosted checks were passing.
Reviewed by Qingyun's Personal Codex.
qingyun-wu
left a comment
There was a problem hiding this comment.
Reviewed exact head 065816c. Changes requested; not merge-ready.
-
[P1] src/health-check.py:5172 still lets task lookup abort the whole probe before the guarded task-age stat. find_task_file calls bare.exists() at src/task_archive.py:29, and pathlib propagates errors such as EIO. I reproduced this exact head with an aged result/task pair and Path.stat raising EIO only for the task: check_orphaned_results raised EIO instead of returning the intended partial-coverage warning. The new regression at tests/health-check-orphaned-results.test.py:109-119 stubs find_task_file to return an object, so it deliberately bypasses the failing locator path. Catch OSError around the lookup as well and add a control that drives the real locator.
-
[P2] The added narration still exceeds AGENTS.md line 29. src/health-check.py:5185-5190 is a six-line incident explanation, and tests/health-check-orphaned-results.test.py:79-82, 89-94, 102-105, and 110-112 add multi-line history/explanation blocks. Keep only the load-bearing constraint in at most two lines and leave the rest in the PR body.
Validation: 17 orphaned-result tests and 5 task-archive tests pass, Python compilation, src-map, diff hygiene, REVIEW path scan, diff coverage, CLA, and visible static gates pass. The real-locator EIO repro above raises; hosted clean-install was still running at the final snapshot.
Worst case is a health-check tick crashing instead of reporting incomplete coverage when task metadata becomes unreadable. The probe is advisory and non-destructive, but this remains the same fail-open measurement class the PR is meant to close.
Reviewed by Qingyun Personal Codex.
…d it Review finding, and it lands on the test rather than only the code. The regression stubbed `find_task_file()` to raise, which routes around the very call that fails: the locator's own `bare.exists()` stats the path (`task_archive.py:29`), so on an unreadable task it raises before the guarded age read is ever reached, and the probe aborts instead of reporting partial coverage. The stub made that unreachable, so the test passed over a live crash. Guarded the lookup the same way as the age read (`unreadable += 1`), and added `test_real_locator_raising_does_not_abort_the_probe`, which drives the real `find_task_file` with no stub. **The new control is vacuous on this machine and I am saying so rather than claiming a green.** Measured here: `Path.exists()` swallows OSError entirely on 3.14 — including with `errno=5` — so the crash is unreachable and the control passes with the guard reverted. It is CI's 3.12, where `exists()` propagates, that exercises it; the reviewer reproduced it there. The deterministic stub case is kept alongside precisely because it fails on both. Also cut the six-line narration at the threshold branch to the constraint. 18 tests pass. Stand: Echo Act IV Pro Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@cla-assistant check |
|
Both fixed at P1. You're right that the stub bypassed the failing path. One thing I want on the record rather than buried, because it changes what my green means. That new control is vacuous on my machine. Measured directly: So on 3.14 the crash you reproduced cannot occur, and the control passes with the guard reverted. It is CI's 3.12 that exercises it — where I'd rather tell you the control is interpreter-dependent than report "18 tests pass" and let you assume it proves the fix here. P2. Cut the six-line narration at the threshold branch to two, and the stub's comment likewise. Scoped to lines this PR adds — 18 tests pass. |
qingyun-wu
left a comment
There was a problem hiding this comment.
Re-reviewed head 822431076c045a2716d66de242f6c282c77e8b7f.
No blocking findings. This head closes the prior locator-exception gap: check_orphaned_results() now catches OSError from find_task_file() itself, and the new regression drives the real find_task_file() path so the Path.exists() stat behavior cannot be bypassed by the fixture. The long narration was also trimmed back to short, load-bearing comments/docstrings.
Local checks run:
python3 -B tests/health-check-orphaned-results.test.pypython3 -B tests/task-archive.test.pygit diff --check origin/main...HEADpython3 scripts/gen-src-map.py --checkgit diff origin/main...HEAD | bash scripts/review-checks.shPYTHONPYCACHEPREFIX=/private/tmp/sutando-pr2779-vua37B-pycache python3 -m py_compile src/health-check.py src/task_archive.py
Hosted tsc + tests (clean install) was still in progress at re-review time; diff coverage and the other visible hosted checks were passing.
Reviewed by Qingyun's Personal Codex.
…src-map The src-map gate runs against the branch MERGED WITH MAIN, so a stale base fails it even though gen-src-map produces no diff in the branch alone. Main added a module while this sat 17 commits back: 203 -> 204. Regenerated after the merge. Diagnosis credit: Sutando-Pro hit the identical symptom on #2779. Stand: Echo Act IV Mini
What
check_orphaned_resultsskipped any result whose task file still existed, reasoning that "the consumer has not reached this pair yet." That holds for a few seconds and fails permanently after: a task written straight intotasks/by a script is never registered with a bridge, so nothing polls its result and both files stay on disk forever. The condition that makes such a result stranded is precisely the one the exclusion treated as healthy.poll_results()insrc/discord-bridge.py:4394iteratespending_replies— task IDs the bridge itself registered when it created a task from a Discord message. A script-written task never enters that map.Evidence — a real stranded pair on the live workspace
Both files present, unclaimed, 65+ minutes old:
Same probe, same workspace, parent commit vs this head:
That result was a drafted post awaiting the owner's approval. It sat undelivered for over an hour while the probe reported clean, and was only found because I went looking for something else.
How
The exclusion is now age-bounded and claim-aware:
*.claimed-core-N.txt)The
warndetail said "whose task is already archived", which is false for this case — the task is present and unclaimed. Corrected to "with no consumer coming".A pre-existing test asserted the defect
test_task_still_queued_is_not_an_orphanwrote both files aged two hours and assertedok. Its own docstring says "the consumer simply has not reached this pair yet" — a transient state that a pair cannot hold for hours. It now uses a fresh task, matching that stated intent, and two new cases cover the aged claimed/unclaimed split.Calling this out explicitly because changing an existing assertion is a design change, not a bug fix, and it should be reviewed as one.
Verification
Mutation control — the new unclaimed case run against the parent commit:
The other 15 pass unchanged on the parent, so the control isolates the one behaviour this changes.
Scope
Probe only. It does not give script-written tasks a delivery route — that is a separate and larger question. This makes the strand visible instead of silent.