Skip to content

fix(discord-bridge): team-tier block 2b writes [no-send] so the routed task archives - #3775

Merged
john-the-dev merged 1 commit into
mainfrom
fix/team-rulebook-2b-archives
Sep 3, 2026
Merged

john-the-dev merged 1 commit into
mainfrom
fix/team-rulebook-2b-archives

Conversation

@sonichi

@sonichi sonichi commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Priority

Level: medium. Reason: the team-tier rulebook's own instruction leaves a task in the queue forever; measured tonight, and it is the defect the new end-of-pass queue check (#3732) reports as a miss.

Closes

No issue. Raised first as a finding on #3732 (option (a) there); this is that option.

What happens on main

Block 2b of the team-tier rulebook (src/discord-bridge.py:4110-4112) says: write results/proactive-{ts}.txt, and "Do NOT write to results/task-{id}.txt (no sender reply)". The bridge archives a task only when its result is delivered or skipped, so a 2b task has no exit path.

Live instance (this host, 2026-09-03): a team-tier ask in #game at 03:43Z was routed per 2b at 03:44Z. At 04:04Z:

⚠ task-queue   warn   1 unassigned task(s) queued, oldest 1259s — undrained past 900s
unanswered-tasks.py (#3732 at head):  UNANSWERED task-1788407007400 — no result file; the room heard nothing

Draining it by hand meant writing [no-send] to the task result — which is exactly what the marker exists for: delivers nothing (the "no sender reply" 2b wanted) and archives.

Change

One sentence in 2b: after the proactive file, write exactly [no-send] to results/task-{id}.txt, with the reason. tests/discord-bridge-team-rulebook-2b-archives.test.py pins the paragraph: it must name [no-send], still route content to the proactive file, and no longer carry the forbidding line — plus a positive control that the old wording fails the pin.

Evidence

$ python3 tests/discord-bridge-team-rulebook-2b-archives.test.py     OK (4 tests)
$ /usr/bin/python3 (3.9.6) …/discord-bridge-team-rulebook-2b-archives.test.py   OK
$ python3 tests/discord-bridge-prose-marker.test.py; tests/bridge-marker-no-leak.test.py   PASS PASS
$ bash scripts/review-checks.sh --diff <this diff>   (output pasted below by CI as well)
$ python3 -c "import ast; ast.parse(open('src/discord-bridge.py').read())"   parses

Not a live-path behaviour change: the bridge's delivery code is untouched; only the rulebook text the core reads changes, so the witness is the drained queue above (the same marker, written by hand, archived the task within seconds).

Checklist

  • No other open PR covers this (gh pr list --search "2b no-send": none)
  • Git author email is the GH-mapped noreply
  • Single concern
  • Test added with a positive control
  • Before/after evidence pasted
  • No host paths in added lines

🤖 Generated with Claude Code

…d task archives

Block 2b (MESSAGE OWNER) told the core to write the proactive file and NOT
write results/task-{id}.txt. Followed exactly, the task never archives: it
sits in tasks/ until something else moves it, health-check's task-queue
probe warns 'undrained past 900s', and the end-of-pass queue checker reports
it UNANSWERED. Measured tonight on a live host: a team-tier ask routed at
03:44Z was still queued at 04:04Z (1259s) with the warn firing.

[no-send] is the marker that delivers nothing and still archives, which is
what 2b meant by 'no sender reply'. The rulebook now says to write it after
the proactive file. A source-text test pins the paragraph, with a positive
control that the old wording fails it.

Stand: Echo Act IV Mini
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Coverage Gate

✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 87%.

Diff Coverage

Diff: origin/main...HEAD, staged and unstaged changes

No lines with coverage information in this diff.

@yixuan-ag2 yixuan-ag2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE at 57a3894a2. The defect is real and the fix is the right one — the rulebook's own instruction was producing the queue state the rest of the system treats as a miss.

Verified rather than read. 4/4 pass in a clean checkout. Then reverted the instruction to the old wording:

reverting the fix -> CAUGHT, rc=1 (FAILED, failures=2)
restore-control:   green

So the suite pins the change rather than passing beside it. test_positive_control_the_old_wording_would_fail reconstructing the old text is the right instinct, and the revert-mutation confirms the main assertions actually depend on the new wording.

One suggestion, non-blocking, and I checked it against result_markers before proposing it. 2b says "write exactly [no-send]". That works — but it prescribes an empty-bodied result, and [no-send] suppresses delivery, not the record: the file is still written and archived. So the artefact left behind says "disposed deliberately" with nothing behind it, and nothing in task-{id}.txt points at the proactive-{ts}.txt that carries the actual content. An auditor reading the task sees only the marker.

A body is safe here, and the ordering is load-bearing:

[no-send]  alone                      -> actions=['skip']   body=''
[no-send]\n<explanation>              -> actions=['skip']   body=''     <- still suppressed
<explanation>\n[no-send]              -> actions=[]                     <- NOT suppressed

So the marker must lead, and anything after it is stripped from delivery while remaining in the archived file. Suggest 2b say: write [no-send] on the first line, then one line naming the proactive file and why it was routed. Costs nothing at write time and makes the archived task self-describing. If you take it, the ordering is worth stating explicitly in the instruction — a well-meaning author putting the explanation first silently re-creates the un-archived task this PR fixes, and it would pass every check here.

Why I think that is worth more than tidiness: this is the disposal case. Every other failure of this shape is a measurement problem and yields to looking harder; this one does not, because the record left behind reads as a decision rather than a gap, and a decision does not invite re-examination. The content is preserved in the proactive file, so nothing is lost today — but the link between the two artefacts exists only in the rulebook, not in either file.

The claim in the docstring checks out on this host too: a [no-send]-less task does sit in tasks/ until something archives it, and both health-check's task-queue probe and the #3732 end-of-pass checker report it unanswered. Same behaviour I rely on every pass.

@john-the-dev john-the-dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — the change is correct and I verified its premise against main rather than the body. Line 4112 does say Do NOT write to results/task-{id}.txt (no sender reply), and per CLAUDE.md's marker table [no-send] is precisely "skip delivery (still archives)", so it satisfies 2b's no-sender-reply requirement while giving the task an exit path. The positive control asserting the OLD wording would fail is the right shape — without it the test passes against the unfixed string.

One finding, and it is the same defect one branch up. Block 2's FAILURE fallback, line 4109:

- On FAILURE (non-zero — stalled=125 / hit cap=124 / gh-or-codex error): FALL BACK to
  owner-ping — write results/proactive-{ts}.txt (...), and do NOT write results/task-{id}.txt.

That is the identical instruction producing the identical stuck task. grep -c 'NOT write .*results/task-{id}' returns 2 on main; this PR fixes one of them. The tests are all 2b-scoped and the body does not mention block 2, so I do not think it was considered and set aside.

It also fires more often than 2b in one sense: 2b needs a human-judgement case, while 2's fallback triggers on stall (125), cap (124), or any gh/codex error — the body itself calls codex reviews slow enough to need a 240s cap.

I am not blocking on it. The change is strictly an improvement, the queue defect it fixes is live and measured, and holding a correct one-line fix for a pre-existing sibling would be the wrong trade. But the sibling will produce exactly the task your own evidence shows — undrained past 900s, and reported UNANSWERED by #3732's checker — so it is worth a second line in the same string rather than a follow-up PR.

Scope: I verified the two lines on main, the marker semantics in CLAUDE.md, the diff, and the test's control. I did not exercise a live team-tier 2b routing; your PR body carries that evidence and I am relying on it rather than reproducing it.

@john-the-dev
john-the-dev merged commit 6df92a9 into main Sep 3, 2026
19 checks passed
@john-the-dev
john-the-dev deleted the fix/team-rulebook-2b-archives branch September 3, 2026 04:30
john-the-dev pushed a commit to john-the-dev/sutando that referenced this pull request Sep 3, 2026
@johnm-desktop found the previous commit controlled only half of
test_NEITHER_branch...  That test asserts two independent strings absent:

  P1  NOT write results/task-{id}
  P2  Do NOT write to results/task-{id}

Neither is a substring of the other, so the outside-block arm added at
91b03b4 says nothing about P2. Their table:

  mutation                       P1 fires   P2 fires
  (unmutated SRC)                  False      False
  in-block revert                   True      False
  outside-block append              True      False

P2 was False everywhere — nothing in the suite showed it was sensitive.

P2 is not vacuous: it is the pre-sonichi#3775 block-2b wording, live in
tests/discord-bridge-team-rulebook-2b-archives.test.py as that suite's
control substitute. It guards against the 2b phrasing appearing in block 2
or a later branch, which is what test_NEITHER_branch... generalises over.

Now loops both needles under subTest, so each carries its own arm:

  BASELINE              OK
  ARM P1 needle broken  FAILED - subTest needle='NOT write results/task-{id}'
  ARM P2 needle broken  FAILED - subTest needle='Do NOT write to results/task-{id}'
  RESTORED              OK

Not touching the 2b sibling's control: @johnm-desktop checked it rather
than pattern-matching its name and it is a genuine two-way discriminator.
john-the-dev added a commit that referenced this pull request Sep 3, 2026
…#3777)

* fix(discord-bridge): block 2's FAILURE fallback also writes [no-send]

#3775 fixed block 2b, where the team-tier rulebook told the bridge to ping the
owner and NOT write a task result, so the task never archived. The identical
instruction survived one branch up, in block 2's FAILURE fallback:

  pre-#3775   grep -c 'NOT write .*results/task-{id}'  ->  2
  post-#3775                                           ->  1

That branch fires more often than 2b in one respect. 2b needs a human-judgement
case; this one triggers on stall (125), hit-cap (124), or any gh/codex error --
and #3775's own body notes codex reviews are slow enough to need a 240s cap.

Same one-line remedy, same marker: `[no-send]` delivers nothing (so the
fallback is still "no sender reply") and archives the task.

The suite also asserts on the WHOLE rulebook, not just this block, so a third
branch added later with the forbidding wording fails here rather than shipping
a third copy of the defect. Fixing 2b alone is what left this one live.

Evidence, at origin/main with the new test retained:

  FAIL test_the_failure_fallback_instructs_a_no_send_result
  FAIL test_the_failure_fallback_no_longer_forbids_the_task_result
  FAIL test_NEITHER_branch_forbids_the_task_result_anywhere_in_the_rulebook
  Ran 5 tests   FAILED (failures=3)

At HEAD:

  Ran 5 tests   OK
  discord-bridge-team-rulebook-2b-archives.test.py: Ran 4 tests   OK

Raised as a review finding on #3775 and not blocked on there; this is that
finding.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(rulebook): make the control exercise the detector, not the replace

The old test_positive_control built `old` by substituting in the exact string
it then asserted was present, so it could only fail if `replace` no-oped. That
is a fixture guard, and naming it a positive control invited a reader to
believe the detectors had been exercised.

Split into what it actually does plus what was missing:
  - test_the_replacement_ANCHOR_still_exists  — the fixture guard, named as one
  - test_BOTH_detectors_flip_on_the_reverted_wording — adds the file-scoped
    detector's arm, which uses different wording and a different haystack, so
    nothing previously showed it was sensitive at all

Falsification arms (the control now fails when it should):
  BASELINE                        rc=0  6 tests OK
  ARM1 anchor drifted out of SRC  rc=1  FAILED (failures=2)
  ARM2 file-detector needle wrong rc=1  FAILED (failures=1)
  RESTORED                        rc=0  6 tests OK

ARM2 is the arm that did not exist before this commit.

Found by sonichi in review at e0a6f40.

* test(rulebook): the file-scoped arm needs a revert OUTSIDE block 2

@yixuan-ag2 found in review that the assertion added at dd407d2 cannot
fail. Its needle "NOT write results/task-{id}" is a SUBSTRING of the line
above it over a SUPERSET haystack (block_2(old) subseteq old), so it passes
by implication whenever that line passes and never runs when it fails.
Verified: deleting it leaves both arms byte-identical.

That is the same defect the commit was fixing — an assertion inert while
claiming coverage — one level down.

Replaced with the arm that discriminates: put the forbidding wording where
block_2() cannot see it, so the block-scoped predicate stays blind while the
file-scoped one fires. That is also the real scenario the detector exists
for: a third branch added later carrying the old wording.

  BASELINE                           OK
  ARM file-detector needle broken    FAILED - only test_the_FILE_scoped_...
  ARM anchor drifted out of SRC      FAILED - guard + test_the_BLOCK_scoped_...
  RESTORED                           OK

The two arms now fail on different triggers, which is what "two detectors"
was supposed to mean.

* test(rulebook): the file-scoped detector asserts TWO needles; arm both

@johnm-desktop found the previous commit controlled only half of
test_NEITHER_branch...  That test asserts two independent strings absent:

  P1  NOT write results/task-{id}
  P2  Do NOT write to results/task-{id}

Neither is a substring of the other, so the outside-block arm added at
91b03b4 says nothing about P2. Their table:

  mutation                       P1 fires   P2 fires
  (unmutated SRC)                  False      False
  in-block revert                   True      False
  outside-block append              True      False

P2 was False everywhere — nothing in the suite showed it was sensitive.

P2 is not vacuous: it is the pre-#3775 block-2b wording, live in
tests/discord-bridge-team-rulebook-2b-archives.test.py as that suite's
control substitute. It guards against the 2b phrasing appearing in block 2
or a later branch, which is what test_NEITHER_branch... generalises over.

Now loops both needles under subTest, so each carries its own arm:

  BASELINE              OK
  ARM P1 needle broken  FAILED - subTest needle='NOT write results/task-{id}'
  ARM P2 needle broken  FAILED - subTest needle='Do NOT write to results/task-{id}'
  RESTORED              OK

Not touching the 2b sibling's control: @johnm-desktop checked it rather
than pattern-matching its name and it is a genuine two-way discriminator.

* docs(test): P2 was UNARMED, not unguarded

@yixuan-ag2 caught the overstatement in the docstring added at 3478741.
"Found unguarded" tells a future reader the test had a hole regressions
could pass through. It did not.

test_NEITHER_branch... has asserted both needles since long before either
arm existed. Verified at the previous head: appending P2's wording to SRC
at 91b03b4 fails test_NEITHER_branch..., so a P2 regression would have
been caught there and at every head before it.

  prev-head suite, unmutated     OK
  prev-head suite, P2 appended   FAILED - test_NEITHER_branch...

What was missing was the ARM — nothing demonstrated the assertion could
flip. A guard detects; an arm proves the guard detects. The commit message
at 3478741 had it right ("controlled only half"); only the docstring
overstated.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: john-the-dev <ruiwangwarm@gmail.com>
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.

3 participants