Repository navigation
fix(health): status readers follow the primary connection, not the legacy sidecar - #5983
Merged
Merged
Conversation
…gacy sidecar After a move (s.space primary, the ag2.space row parked) the legacy gateway-status.json goes stale by design, and the gateway-bridge check read it as the primary and warned that messages may not be delivered. A fresh install whose only gateway is a connection row read as unconfigured. - connections: read-only helpers read_rows / primary_row / primary_server / enabled_rows and sidecar_name; CLI verb `enabled`. - health-check: the primary row's sidecar is the gateway's own; lane scans skip the primary's lane and parked/disabled rows (via status_for); messages name the server instead of a hardcoded one. - "is a gateway configured" (health-check, runtime-health, navigate_ui) is also true when any enabled row exists; the voice tool asks the CLI, not a copy. - dotspace: current_server / default_link_via / default_cloud_origin take the caller's primary_server before the legacy fallback (vendored copy synced). - whoami and rundir report the primary row's agent_mxid first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ter in the CLI-backed probe test main gained an identical read-only primary_row, so this branch's copy is dropped. A Linux runner's /usr/bin/python3 reads as the macOS stub to resolvePython, so the test names its interpreter through SUTANDO_PY. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
liususan091219
requested changes
Oct 28, 2026
liususan091219
left a comment
Collaborator
There was a problem hiding this comment.
Requesting changes: the health-check readers now follow the primary row, but two sibling readers of the same sidecar are left on the legacy file.
- Bandaid, sibling sites:
src/core-input-watch.py:529(_gateway_status, which feedsgateway_alive()and so the core'sgateway-downstate) andsrc/services_status.py:73(GATEWAY_STATUS_PATH, the desktop services row) still hard-codegateway-status.json. On the moved install this PR targets, that file is the parked bridge's stale sidecar, so both always read "no opinion" and fall back to the identity-blind pgrep their own docstrings call a proxy for the wrong thing.connections.sidecar_name(primary.instance)added here is the fix for both. Please move them too, or file an issue and name them under Follow-ups. - Nit, non-blocking:
_gateway_lane_label(ln)withoutrows(src/health-check.py:6956,:6958,:6966) runs_gateway_rows(), which callsstatus_for(sidecar read plus lease probe) on every row, once per lane._gateway_lanes_not_servedalso needs only the rawprimary/disabled/parkedflags, soread_rowswould do. - Checked, fine: disabled rows derive
state == "parked"(derive_state), so the lane skip covers both, as the body says. Tests import and exercise the real modules, with no source-text assertions. The new suite andhealth-check-gateway-bridgepass locally at head. TheSUTANDO_WORKSPACEintests/sutando-whoami.test.shis a test-mode fixture, not a default. - CI: python shard 1/9 fails on
tests/remote-gateway-bridge.test.py. It also failed on main at b72b5ca, and main fixed it in 9826add (#5984). A rebase should clear it. license/cla is present. - There are no approvals yet.
— Sutando (susans-MacBook-Pro)
…primary connection's sidecar One resolver, connections.primary_sidecar_name, now serves health-check, core-input-watch and services_status. Per-lane labels and the lane skip read stored rows only, with no sidecar read or lease probe per lane. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
Author
|
Addressed in 8881b56 (main merged in):
|
Contributor
|
@cla-assistant check |
Contributor
Coverage Gate✅ Diff coverage PASSES the 95% bar. Whole-tree (informational): 94%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changes
Summary
|
bassilkhilo-ag2
added a commit
that referenced
this pull request
Oct 29, 2026
main gained its own answer to "which gateway-status sidecar do the readers read": #5983 routed health-check, core-input-watch and services_status through connections.primary_sidecar_name (the primary connection row's suffixed file, the unsuffixed one without a primary row), and #5971 / #6008 reworked the neighbouring health-check text and the configured-gateway probe. This branch routed the same three readers through gateway_serving.status_path, the desktop's active-lane selector. Both are kept: the active lane wins when the selector names a valid lane whose file exists (an s.space-only desktop install, where s.space is a secondary lane and no row is primary), else the primary connection's sidecar as main resolves it. Conflicted files and how each was resolved: - src/gateway_serving.py (auto-merged, edited): status_path's fallback is now connections.primary_sidecar_name(state_dir) via a lazy import instead of the literal gateway-status.json, so one resolver carries both rules and no reader composes them privately. Without a primary row that name is the unsuffixed file, so the branch's fallback cases are unchanged. - src/core-input-watch.py: _gateway_status reads gateway_status_path(state_dir) (this branch); main's private _primary_sidecar_name helper is dropped because its logic now lives in the shared resolver; gateway_alive's docstring names both sources. - src/services_status.py: main's no-argument gateway_status_path(), which services-status.test.py pins, is kept and returns status_path(STATE_DIR); the import alias becomes _lane_status_path so the function no longer shadows it (the raw auto-merge had the def shadowing the import). Main's registry comment is kept with "active lane, else primary" wording; the probe calls gateway_status_path(). - src/health-check.py: all five default-path sites keep main's _gateway_primary_sidecar() (pinned by health-check-gateway-primary-connection .test.py); that helper now resolves the name through status_path and still passes it through status_read_path for the legacy workspace-root fallback. This branch's _gateway_status_path helper is removed as redundant. Tests run at this commit, all green: gateway-status-active-lane, gateway-serving-shared-owner, health-check-gateway-bridge, health-check-gateway-primary-connection, health-check-sspace-migration, health-check-gateway-lane-verdict, health-check-gateway-stale-lane, health-check-gateway-transient-outage, health-check-gateway-outage-duration, health-check-gateway-probe-unknown, health-check-notify-gateway, gateway-connected-requires-last-ok, gateway-age-helpers-overflow-safe, gateway-runtime-identity, core-input-watch-gateway-probe, core-input-watch, services-status-liveness-patterns, services-status, core-health-verdict, util-paths-runtime-identity-absent. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed, and why
After a move from ag2.space to s.space, the s.space row is primary and the legacy ag2.space row is parked. Several readers still assumed the legacy unsuffixed bridge was the install's gateway:
src/health-check.pycheck_gateway_bridgeand the sidecar readers_gateway_last_ok_age_h,check_runtime_identity,_gateway_status_stale_age_s,_gateway_status_ts_malformed,_gateway_serving(each defaulted togateway-status.json)connections.sidecar_name(primary.instance)). Lane scans skip the primary's own lane and every parked or disabled row's lane, found by walking the rows withstatus_for. Messages name the primary's server, and lanes read aslane (server)_gateway_configured(health-check),_gateway_configured(src/runtime-health.py),navigateUiAvailable(skills/ag2space-voice/navigate.ts)channels/ag2space/.env, so a fresh s.space-only install read as unconfiguredconnections.enabled_rows. The TS side asks it through the CLI (scripts/connections.py enabled) instead of copying the rulesrc/dotspace.pycurrent_server→default_link_via/default_cloud_originfell back toLEGACY_SERVERAGENT_MXIDlinked via ag2.spaceprimary_server=; dotspace stays stdlib-only and reads no record. Vendored copy synced withtools/sync_from_src.py(drift test green).src/ag2space_links.pypasses it, behind a guarded import so a skill without core still falls backscripts/sutando-whoami.sh,src/runtime-api/rundir.pyenrolled_agent_idagent_mxidfirst, then the old sourcesPrimary-row helpers in
src/connections.py, for other connections work to reuse: this branch uses the read-onlyprimary_row(workspace)that #5980 added on main, and addsread_rows(workspace)(every row, read-only, never imports from the environment, unlikelist_connections),primary_server(workspace),enabled_rows(workspace)andsidecar_name(instance)._sidecarnow usessidecar_name. None of them toucheslaunch_envor room-to-connection resolution.The user-facing health-check wording became server-neutral here, not in the docs PR, because this PR rewrites those lines:
GATEWAY_DOWN_DETAIL, the not-serving/stale details, and "gateway not configured on this host". The health-check fragments intests/dotspace-literal-guard.test.py's allowlist are now unused. They are left in place so this PR does not conflict with the docs PR's edit to the neighbouring allowlist line.Where to look first
src/health-check.py(new_gateway_*helpers beforeGATEWAY_STATUS_MAX_AGE_S, the default-path readers, the lane skips), thensrc/connections.py(helpers afterprimary_instance), thensrc/dotspace.pycurrent_server.Evidence (before / after)
A moved install: s.space primary, ag2.space legacy row parked with a day-old
gateway-status.json, a freshgateway-status.s-space.json, no legacy token anywhere. Same script against origin/main and this branch:The new suite at origin/main:
FAILED (failures=3, errors=13). At HEAD:OK(16 tests). Mutation check: reverting only the sidecar default path togateway-status.jsonfails the moved-install, silent-primary and three-server cases.Tests
New,
tests/health-check-gateway-primary-connection.test.py:check_runtime_identityreads the primary sidecar.connections.py enabledexits 0 withs-space. Parked or disabled rows alone configure nothing (exit 1).lane s-space (s.space)).primary_serverdrivescurrent_serveranddefault_link_via.current_serverprecedence: mxid, then gateway, then primary, then "", and an invalid primary is ignored.default_cloud_origin(primary_server=)stays within configured entries. Links use the primary. rundir reads the primary, then the enrollment record, and falls back when the record raises.Updated:
tests/ag2space-voice-navigate.test.ts(connection-only install offers navigation.enabledConnectionExistsagainst a real record: no record, parked plus disabled, s.space-only),tests/sutando-whoami.test.sh(primary row's agent wins),tests/health-check-gateway-bridge.test.py(pins an empty workspace so the host's own rows never leak into the legacy-token cases).tests/health-check-fix-down-bridges.test.pycase f fails identically on origin/main on a host whose own Slack bridge is running. That is a pre-existing host dependence and not touched here.tests/runtime-api-request-expiry.test.pyfailed once during the long batch run and passes when run alone (rc 0). In CI,tests/remote-gateway-bridge.test.py(python shard 1/9) also fails on main at b72b5ca, before this PR.Feature/documentation consistency
docs/connections.mddocuments the read-only helpers, theenabledCLI verb and what the health check reads.docs/dotspace.mddocumentsprimary_server=.docs/catalog.jsondates are bumped.Risks / edge cases
rundir.enrolled_agent_idnow prefers the primary row's agent, so the per-actor run dir key follows a primary flip. A runtime daemon started before the flip keeps its old socket until it restarts.util_paths' canonical actor uses the same function, so the two sides agree.cloud_auth.DEFAULT_CLOUD_ORIGINis deliberately unchanged. For every registered server without its own Cloud the answer is the same, and changing the credential owner's default deserves its own review.Follow-ups
--notify-gateway(_gateway_credsand the alert path) belongs to the shared room/connection resolver work (fix(connections): room tools act through the connection serving the room's server #5977 / fix(connections): every gateway client acts through the room's own connection #5980, now on main) and is deliberately not touched here.tests/dotspace-literal-guard.test.py.🤖 Generated with Claude Code