fix(gateway): classify real token locks and keep lifecycle running - #83183
fix(gateway): classify real token locks and keep lifecycle running#83183alexgunsberg wants to merge 3 commits into
Conversation
Salvage optional-platform parking from NousResearch#83161 without its unsafe assumptions. - Inspect lock/polling conflict semantics before retry routing so production _acquire_platform_lock failures (retryable=True) still exit 78 on pure single-writer conflicts and are not retry-queued. - Surface multiplex secondary lock_conflict through the same classifier instead of silently disconnecting. - Keep gateway_state=running with per-platform fatal/retrying detail so busy/drain contracts remain valid for active cron/Kanban work. - Park Buzz-style platform-local auth/config failures without killing scheduler/Kanban or writing a non-drainable degraded lifecycle state.
|
Release follow-up at exact head |
|
Independent exact-head review: CHANGES REQUIRED Reviewed head: 5a44eb7 Three HIGH findings:
Do not merge or deploy this head. Repair the three findings, add production-shape regressions, rerun focused gateway/status suites, and request a fresh exact-head review. |
Repair three HIGH residuals on the platform-isolation candidate: - Secondary multiplex retryable startup failures enter the profile-scoped reconnect registry after _running=True instead of suppressing exit 78 with a live-but-deaf gateway. - Classify Telegram production 409 getUpdates conflicts even when wrapped as generic telegram_connect_error so zero-connected startup still fails closed with exit 78. - Unpack acquire_scoped_lock()'s (acquired, existing) tuple in Buzz, IRC, and LINE so lock-conflict branches are reachable. Add production-shape regressions for all three defects.
HIGH-3 residual repair at exact headHead: Repairs the three independent-review HIGH findings only:
Verification (isolated HERMES_HOME): 166 passed, 2 skipped across focused gateway startup/status/adapter suites; Ruff + compileall + No merge/deploy requested — awaiting independent exact-head review. |
|
Independent exact-head review: CHANGES REQUIRED Reviewed head: The prior three HIGH findings are repaired in their direct paths, but two additional HIGH lifecycle/ownership defects were reproduced:
Required regressions:
Independent evidence: 144 focused tests passed, Ruff passed, diff-check passed; two adversarial probes reproduce the blockers. Do not merge/deploy this head. No repository files were modified by review. |
Repair two HIGH lifecycle/ownership defects on the platform-isolation candidate: - Transient secondary startup failures now reserve credential/listener claims in the shared ownership registry so a peer profile cannot consume the same Telegram token before reconnect. Reconnect consults that registry before any external connect/publication. - After awaiting secondary initial connect, fence publication on startup/shutdown state; if stop() already drained the registry, disconnect the late-success adapter and do not register it. Add adversarial regressions for both defects. Preserve prior HIGH-3 repairs (409 classification, lock-tuple unpack, secondary retry arming).
HIGH-2 ownership/shutdown repair at exact headHead: Repairs the two independent-review HIGH findings only:
Prior HIGH-3 repairs preserved: telegram_connect_error 409 classification, Buzz/IRC/LINE lock tuple unpack, and normal secondary retry scheduling. Adversarial regressions added
Verification (isolated HERMES_HOME / worktree venv)
No merge/deploy requested — awaiting independent exact-head review. |
|
I found one additional reconnect ownership race on exact head
During an Minimal probe: runner = GatewayRunner.__new__(GatewayRunner)
runner.adapters = {Platform.TELEGRAM: TokenAdapter("shared-token")}
runner._failed_platforms = {}
runner._profile_adapters = {}
runner._profile_retry_resource_claims = {}
monkeypatch.setattr(
"hermes_cli.profiles.get_active_profile_name", lambda: "alpha"
)
owner = runner._secondary_reconnect_claim_conflict(
"alpha", Platform.TELEGRAM, TokenAdapter("shared-token")
)
assert owner == "default"Actual: The primary registry owner needs to be fixed/captured independently of the current secondary profile scope (for example, an explicit runner primary owner established at startup). A regression should cover: primary holds |
|
too large to review safely This PR changes 690 production lines before tests and docs. Please split it or add a focused justification if it should stay together. Signed: GPT-5.6-luna-high in Codex |
|
Maintainer review at exact head The direction is right and wanted: pure single-writer token-lock conflicts at zero-connected startup should exit 78 instead of retry-queueing forever, platform-local auth/config failures (Buzz membership, missing credentials) should park fatal per-platform while the lifecycle stays What blocks the merge:
No split demanded — the classifier + lifecycle + lock-tuple changes are one coherent contract and splitting them would leave intermediate heads in the live-but-deaf state the PR exists to eliminate. But (1) is a real duplicate-poller vector and can't ship. Once (1) is repaired with a regression and the suites re-run at the new head, ping and we'll re-review promptly — the underlying fix is wanted. |
fix(gateway): classify real token locks and keep lifecycle running Well-scoped fix with strong regression coverage (real
|
Bug Description
Optional platform startup auth/config failures (production: Buzz
relay_membership_required) previously forced gateway exit 78 and stopped scheduler/Kanban. PR #83161 tried to isolate them but was closed after independent BLOCK: production token-lock conflicts are emittedretryable=Trueand bypassed the fatal classifier, andgateway_state=degradedis neither busy nor drainable.Root Cause
BasePlatformAdapter._acquire_platform_lockmarks live foreign holdersretryable=True(for later reconnect). Startup routed solely on that flag, so pure single-writer conflicts stayed alive / retry-queued instead of exit 78.degradedbreaksderive_gateway_busy/derive_gateway_drainable(onlyrunningis drainable).Fix
is_global_startup_conflict()and classify lock/polling ownership before generic retry routing.running; per-platform fatal/retrying holds detail (no non-drainable degraded lifecycle).How to Verify
uv run --extra dev pytest \ tests/gateway/test_runner_startup_failures.py \ tests/gateway/test_stale_platform_lock_retryable.py \ tests/gateway/test_runner_fatal_adapter.py \ tests/gateway/test_platform_reconnect.py \ tests/gateway/test_status.py::TestGatewayBusyDerivation \ tests/gateway/test_multiplex_adapter_registry.py \ tests/gateway/test_multiplex_pairing_stores.py -q # 59 passed uv run --extra dev ruff check gateway/restart.py gateway/run.py \ tests/gateway/test_runner_startup_failures.py \ tests/gateway/test_multiplex_adapter_registry.py \ tests/gateway/test_multiplex_pairing_stores.pyTest Plan
_acquire_platform_lock(retryable=True) exits 78, not retry-queuedrunning; Kanban watchers spawn; busy/drain true with active_agentsRisk Assessment
Low–medium. Narrow startup classification change; pure lock conflicts and multiplex config errors keep exit-78; optional platforms no longer take down duties; lifecycle busy/drain preserved.
Notes