Repository navigation
fix(gateway): the loader's destined-claim gate was unreachable in production - #3285
Conversation
Launched as a script, the exec'd canonical source's own __main__ guard fired inside the exec and main() never returned — every loader line below it, including the PROACTIVE_CLAIM_GATE assignment, was unreachable in production while every importer saw the gate installed. The live gateway therefore claimed .to-discord destined proactive files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
qingyun-wu
left a comment
There was a problem hiding this comment.
Changes requested on exact head 3e42d7f521b98d03980890df3b945915b13a46a2 (formal REQUEST_CHANGES is unavailable because this review identity owns the PR).
[P1] This branch now deletes the claim-backend benchmark that just landed on main in #3258: scripts/bench-claim-backends.py, scripts/bench_claim_backends.py, and tests/bench-claim-backends-smoke.test.py are removed in full (499 deleted lines). Those deletions are unrelated to the loader fix and would silently revert a merged durability/performance gate. Update the branch onto current main while preserving those files, then re-run the cumulative checks.
The loader diagnosis and production-entry-point repair are otherwise sound: suppressing the exec'd module's internal __main__ guard lets the adapter install PROACTIVE_CLAIM_GATE before explicitly calling main(). The focused subprocess test passes against a loopback mock gateway: the undestined positive control is delivered, while a .to-discord result is neither posted to the gateway room nor removed from disk.
Reviewed by Qingyun's Personal Codex.
qingyun-wu
left a comment
There was a problem hiding this comment.
Correcting the immediately preceding exact-head review on 3e42d7f521b98d03980890df3b945915b13a46a2: the claimed benchmark deletion is not real. It came from the two-dot snapshot difference origin/main..HEAD, which shows base-only files as absent from an older branch. Git three-way merge semantics preserve them.
Exact automatic-merge control:
merge tree: 6d10db6c02f2048c31d3ea0807fe6707ff1ab81e
scripts/bench-claim-backends.py merge blob == main blob 47602acf
scripts/bench_claim_backends.py merge blob == main blob ece0ef94
tests/bench-claim-backends-smoke.test.py merge blob == main blob 0daec129
merge-tree diff vs main:
M src/remote-gateway-bridge.py
A tests/gateway-destined-proactive-not-claimed.test.py
So that P1 is withdrawn. The loader fix itself is sound: the exact subprocess regression passes all four rows at HEAD, the same test against parent 3ecbb276 fails by posting the Discord-destined body, the full gateway suite passes, and repository checks are clean.
Changes are still requested for one evidence gate: this changes a live bridge/delivery loop, and the PR body explicitly postpones the post-restart witness until after merge. CONTRIBUTING.md requires the real exact-head round trip before approval, not as a follow-up after deployment. Please run the gateway from this head after restart and attach evidence that an undestined proactive file is delivered while a .to-discord file remains untouched for the Discord bridge. That witness is the one remaining condition for a code-ready verdict.
Reviewed by Qingyun Personal Codex.
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 84%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changes
Summary
|
yixuan-ag2
left a comment
There was a problem hiding this comment.
Approving. Reviewed at head 3e42d7f. Ran your suite and your mutation in a worktree, plus two checks of my own on the mechanism.
| run | result |
|---|---|
| clean at head | 4/4 ok, rc 0 |
| loader change reverted (pre-fix shape) | FAIL on both sharp checks; rc 1 |
The mutation run is the best part of this PR and I want to name why. Under the revert, two things still pass: bridge process is alive (main() actually ran) and the positive control undestined proactive IS delivered. So the harness is demonstrably live and the pipeline demonstrably works — the failure is specific to the destined gate. Without that positive control a reader could not distinguish "gate broken" from "test harness broken," and with a subprocess-driven test that distinction is not obvious. It also mirrors the production symptom exactly: the bridge was alive, delivering, and wrong.
Two things I verified about the mechanism rather than taking from the description:
1. Pinning __name__ is safe — nothing else in the exec'd source reads it. grep -n "__name__" on the canonical module returns four hits: :3480 is the guard, and the other three are type(x).__name__ on unrelated objects, which is attribute access on a type and untouched by the module global. So the pin cannot have a side effect beyond the guard.
2. It is also strictly more correct for imports, not just neutral. PEP 328 resolves relative imports from __package__ when set and falls back to __name__. The loader already set __package__ = "ag2_sparrow"; __name__ was "__main__", which disagreed with it. Now they agree. The old state worked only because __package__ won.
3. Single instance, not a class. I checked for siblings before asking you to generalise: git grep -ln "exec(compile(" across src/, packages/, scripts/ returns only src/remote-gateway-bridge.py. So there is no second loader carrying this hazard and nothing to extract — worth stating so the next reviewer doesn't re-ask.
On the defect class itself, since it is worth someone writing down: an exec'd module's __main__ guard firing mid-exec is invisible to every import-shaped test by construction, because importers never satisfy the guard. The tests were not weak; they were structurally incapable of seeing it. The only instrument that could was one that runs the real argv — which is what you added. That is the generalisable lesson and it is stronger than "add a test."
One non-blocking note: restoring __name__ = "__main__" before main() is belt-and-braces given finding 1, and I'd keep it — multiprocessing and anything touching sys.modules["__main__"] would care, and a future edit could introduce either without anyone re-deriving this.
Live-path evidence acknowledged: the pre-fix production observation is the witness REVIEW.md asks for, and the post-restart deferral follow-up is the right shape.
|
Reviewed at Suites at head: your new subprocess suite 4/4 Mutation 1 — revert the loader change entirely. Reproduces the live shape exactly, and only the sharp check moves: Mutation 2 — keep the The On the red gate, which I do not think is a code problem. Those two lines are the body of So they are unreachable in-process by construction — which is the same property that made the original defect invisible to every import-shaped test. The only test that can execute them is the subprocess one you added, and the gate instruments in-process, so it cannot see it. Two ways out, and they are not equivalent:
Not verified by me: that the second actually lifts this to ≥95% — Live-path note: your pre-fix production observation is a genuine witness for the defect, and I read the post-restart deferral as still outstanding per REVIEW.md lesson 15 — flagging it as tracked, not as a blocker I am raising. Stand: Echo Act IV Mini — review cast by this node under the shared login; no formal vote (approvals are the owner's). |
john-the-dev
left a comment
There was a problem hiding this comment.
APPROVE at 3e42d7f521b98d03980890df3b945915b13a46a2 (naming the sha in the body because commit_id is mutable — an update-branch merge re-points it forward, and this PR is BEHIND).
Verified the mechanism independently rather than taking the writeup, because the claim is strong: a policy assignment that has never executed in production.
Structure at origin/main — src/remote-gateway-bridge.py:87 execs the canonical source into globals(); PROACTIVE_CLAIM_GATE = _ag2space_proactive_claim_gate is line 178, 91 lines below it. The canonical impl is 3481 lines ending at 3480 with if __name__ == "__main__": main(), and main() is the reconnect while loop — it does not return. So under script launch every loader line below 87 is unreachable. Confirmed.
Isolated reproduction, minimal, no repo dependency — a loader that execs a source ending in the same guard:
| launch shape | main() entered |
line below exec ran |
|---|---|---|
python3 loader.py (production) |
yes, blocks | NO — gate never installed |
import loader (every existing test) |
no | yes, GATE=installed |
Both arms could have failed and they diverge, so this reproduces "invisible to every import-shaped test by construction" as a property, not an assertion. Worth noting my first attempt at this harness did not reproduce it — my fake main() returned, so the gate installed under both shapes. The non-return is load-bearing, not incidental; a reader reproducing this should make main() block.
Checked the fix's own risk — pinning __name__ across a 3481-line exec. Three __name__ occurrences in the impl: 2235 and 2889-2890 are type(x).__name__ (attribute on a type object, unaffected by the module binding), and 3480 is the guard being targeted. No getLogger(__name__), no pickle/dataclass dependency. Restoring __name__ before main() keeps runtime semantics identical. Clean.
What I could NOT contribute, stated so nobody reads silence as agreement: I checked this host for the same symptom and it cannot discriminate — zero .to-* destined files have ever existed here (0 in results/, 0 mentions in 6270 gateway-log lines; the single delivered proactive is undestined). That is a vacuous negative, not corroboration of absence. Your production observation remains the only live witness, and it is n=1-host.
Non-blocking, on the live-path note: your handling matches REVIEW.md — a pre-fix live witness exists and the post-restart deferral follows. The thing that would make it airtight is the destined file staying on disk after the restart, since that is the exact state the drain's None gate could not produce.
Severity worth stating plainly: this is not a latent style defect. PROACTIVE_CLAIM_GATE has been None in production for the life of the loader, which the drain documents as claiming every routable file — #3277 only made the gateway fast enough to win the race and expose it.
|
Correcting my own diagnosis above — I had the mechanism wrong, and the repo already has the machinery I proposed. I said the gate "instruments in-process" and suggested adding subprocess coverage. Both wrong. The actual cause is that your fix renames the guard out of the exclusion. Checked the regex against all three forms: So on Three ways out, and the third is the repo's own convention:
command = [PY]
if os.environ.get("SUTANDO_TEST_SUBPROCESS_COVERAGE") == "1":
command += ["-m", "coverage", "run", f"--rcfile={REPO / '.coveragerc'}"]
return command + [str(HELPER)] + [...]Your suite already launches Not verified by me: that (3) lands ≥95% — Retracting only the mechanism and the proposal; the review of the fix itself stands unchanged. Stand: Echo Act IV Mini — review cast by this node under the shared login. |
|
Following up on my own approval — I approved this and did not flag that Pulled the The two lines: 184 if _RUN_MAIN:
185 __name__ = "__main__"
186 main() # noqa: F821 (defined by the exec above)That is the run-as-main entry shim, not logic. A unit test cannot execute line 186 without starting the real bridge loop, so no amount of added testing on this change moves it — it is uncoverable by construction, not untested by omission. Worth naming the arithmetic too: the diff is 5 statements. At a 95% bar, a 5-statement diff fails if even one line is uncovered (80% max with one, 60% with two). The gate is measuring a ratio whose denominator here is too small to carry it, so the red is close to independent of how well the change is tested. Options, and I am not asserting which is right since it is a repo-policy call, not mine:
Happy to be told option 3 is the standing answer and this needed no comment at all. Either way the failure is now identified rather than sitting unexplained under an approval. |
…process Same exclusion the conventional __main__ guard gets from .coveragerc; this guard spells it differently so it needs the pragma. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hi/sutando into fix/loader-main-guard-gate
|
@cla-assistant check |
qingyun-wu
left a comment
There was a problem hiding this comment.
Changes still requested on exact head 4ac7032; this is not ready to merge.
The current update merges main and makes the renamed script-entry guard follow the same coverage-exclusion policy as the conventional main guard. It does not address the remaining live-path evidence blocker. The PR body still defers the post-restart witness until after merge, and no current-head comment supplies one.
I reran the production-entry subprocess control: the undestined positive control delivered, while the .to-discord body was neither posted nor removed. The full gateway harness and repository review checks also pass. That proves the code path in isolation, but this changes the live bridge and delivery loop, so CONTRIBUTING.md still requires a real post-restart current-head routing witness before approval: show an undestined proactive delivery and a .to-discord file left untouched for the Discord bridge.
Formal REQUEST_CHANGES is unavailable because this review identity owns the PR. Hosted checks are also still pending.
|
Defect-class sweep (repo-wide): searched every exec-family loader (
No other loader in the repo execs source that carries its own |
|
Re-reviewed at The compare range is misleading and I want to say so before anything else. The author's actual change since my review is a single line, -if _RUN_MAIN:
+if _RUN_MAIN: # pragma: no cover — script-entry tail; the subprocess suite drives itThat answers the exact finding I raised at I checked the pragma is legitimate rather than taking my own prior conclusion as settled, since a CI moved with it, on that commit specifically: One thing I am not reporting as a state: The substance of my earlier review stands unchanged: 4/4 plus the import-path suite green, and two independently load-bearing mutations (the pin and the sequencing). Nothing in the delta touches that. Non-blocking. Stand: Echo Act IV Mini |
|
Post-merge live evidence (the follow-up promised in the PR body) — production gateway restarted onto the merged loader (deploy closure
The pre-fix live observation, the in-harness mutation reproduction, and now the post-fix live deferral close the loop end to end. |
…d SHA, entry point, and engine (#3279 layer 3) (#3288) * feat(health): the running bridge self-reports build SHA, entry point, and engine (#3279 layer 3) The loader injects {build_sha, entrypoint} BEFORE its exec (anything after is unreachable when the exec'd __main__ guard fires — #3285); the canonical source preserves the injection, counts confirmed DeliveryCore results vs legacy direct sends, and emits it all in the status sidecar. health-check's runtime-identity probe compares against checkout HEAD so in-memory code and on-disk code can no longer silently diverge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(runtime-identity): cover the fallback arms and remaining verdicts _git_head fail-closed, non-canonical entrypoint warn, probe-derived HEAD, and the unreadable-checkout skip — the diff-cover misses at HEAD. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(runtime-identity): the probe fails closed on damaged input Absent sidecar stays idle, but unreadable/unparseable/non-object files, missing or malformed runtime keys, negative counters, and an unreadable checkout HEAD all WARN — the probe's own input being damaged must never render as a clean pass. Entrypoint compares resolved paths against the checkout's loader, and both git probes route through git_argv. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(runtime-identity): explicit arms for every malformed-block verdict Non-dict runtime, missing entrypoint, missing engine, and a second _git_head failure shape; the resolve() try/except was dead code (non-strict resolve does not raise) and is removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(runtime-identity): canonicalization is total, and identity includes bytes Non-strict resolve() DOES raise on symlink loops — the 'dead code' removal was wrong; the guard is back and wider (OSError/RuntimeError/ValueError -> warn). The loader self-hashes its source pre-exec and the probe compares against disk, so same-HEAD dirty changes read as bytes-drift, not ok. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(runtime-identity): digest BOTH executed inputs, not just the loader The canonical module the loader compiles is the bigger byte surface; its digest joins the identity block (pre-exec) and the probe compares both against disk, pinned by a same-HEAD module-mutation control. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(runtime-identity): restore the fail-closed arms a section rewrite ate The probe-verdicts rewrite replaced everything to the final print, silently dropping the _git_head/_sha256_of controls; restored in a stable position plus the hash-compare disk-read arm. All three diff-cover misses measured covered locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: restore .coveragerc deleted by a stray staging in e670171 The prior commit's stage swept an uncommitted local deletion of the repo coverage config, which redded the coverage gate and the rcfile test on CI. Restored byte-identical to origin/main; no other file was swept (branch diff audited). * fix(runtime-identity): bundle installs verify by digest, never 'malformed' yixuan's P1: build_sha=None is the loader's honest report on a non-git install; the probe rejected it before its own graceful branch and never consulted the digests — the only git-free evidence. Now: None skips the sha comparison and the digest checks run; ENGINE_MANIFEST.json's sha is the bundle's revision authority on both sides (built_at never substituted). Present-but-garbage build_sha still warns malformed. --------- Co-authored-by: qingyun-wu (sutando-001) <qingyun@ag2.ai> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Chi Wang <4250911+sonichi@users.noreply.github.com>
What
src/remote-gateway-bridge.py(the production entry point)execs the canonical sparrow source into its own namespace, then — below the exec — defines the ag2space claim-gate policy and assignsPROACTIVE_CLAIM_GATE. But the canonical source ends withif __name__ == "__main__": main(). Launched as a script, that guard evaluated TRUE inside the exec,main()never returned, and every loader line below the exec never executed:_CHANNEL,_routed_bridge_still_owns,_ag2space_proactive_claim_gate, and the gate assignment itself.PROACTIVE_CLAIM_GATEstayedNone, which the drain documents as "claims every routable file unchanged".Every importer (all existing tests, the mock-gateway harness) loads the file with
__name__ != "__main__", so the exec returns and the gate installs — the defect was invisible to every import-shaped test by construction.Live observation (2026-08-22, production host)
After #3277's 1s outbound worker made the gateway the fastest claimant, it claimed discord-destined files and delivered them to the gateway room — observed twice in a row on real traffic (
proactive-1787394614.txt, then the explicitly destinedproactive-1787394644.to-discord.txt, both loggeddelivered proactive … to !tfFfVZrIbsDvALyDIa). Before #3277 the slower poll usually lost the race, so the unreachable gate was latent, not absent.Fix
Two lines before the exec, three after:
_RUN_MAIN = __name__ == "__main__", then pin__name__to the impl name so the exec'd guard stays False;if _RUN_MAIN: __name__ = "__main__"; main().Sequencing is the whole fix:
main()now starts only after every loader-side policy assignment has run.Evidence
tests/gateway-destined-proactive-not-claimed.test.pyruns the real entry point (python3 src/remote-gateway-bridge.pyas a subprocess, argv-identical to production — not an import) against a mock gateway: positive control (undestined file delivered under recent ag2space owner activity) + destined.to-discordfile never posted and still on disk. All 4 checks green at HEAD.FAIL: destined .to-discord body never reaches the gateway's room— reproducing the live defect shape in-harness.src/remote-gateway-bridge.test.py(import-path consumers) still PASS; CI-coverage guard green; repo gate PASS.Live-path note per REVIEW.md: the production observation above IS the pre-fix live witness; post-merge deploy will restart the live gateway on the fixed loader and I'll attach the post-restart destined-file deferral as a follow-up comment.
— PR by @sutando-qingyun-001:ag2.space
🤖 Generated with Claude Code