Repository navigation
fix(connections): every gateway client acts through the room's own connection - #5980
Merged
Merged
Conversation
…oom's server On an install connected to several homeservers, room_ops, notify and channel-env.sh always resolved the legacy unsuffixed credential, so an upload to a room on another server got 403 "not a member" from the wrong gateway. connections.connection_for_room/room_credential is now the one owner of that choice: the row whose server equals the room ID's domain exactly (never a suffix match), the lane with a task open in a domainless room, else the primary; no room is the primary. A domain no row serves, a disconnected row, or a row with no token for its own gateway is refused by name. Fewer than two rows keeps every caller's existing chain. room_ops' gateway(room_id), notify's _gateway_config and channel-env.sh --room delegate to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nnection The pending-questions and Crons room databases, the tutorial page, ensure-cron-room, cron-notify, bee-actions and health-check's gateway alert now ask connections.room_credential which connection to use: the room's own server (exact domain match), the primary when there is no room. A room no connection serves is refused, never answered with another server's token. Single-connection installs keep their existing chains. - channel_credentials.room_env: the shared answer for clients that read credential variables (room_collab / room_commons). - owner_room_access reads the primary row's owner-routing.<instance>.json, falling back to the unsuffixed file. - connections.primary_row: a read-only primary lookup. - The context-first prompt passes --room to channel-env.sh (both copies). - room_ops verbs that name no room are documented as acting on the primary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…paths of the gateway clients Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ils without routing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…room-resolver # Conflicts: # docs/connections.md # src/connections.py
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
@cla-assistant check |
This was referenced Oct 28, 2026
6 tasks done
sonichi
added a commit
that referenced
this pull request
Oct 30, 2026
…eature list and rebuilds when the list is newer Split out of #5839, which narrows to the on_launch launch-hook capability. The tour content is data (skills/tutorial/feature-list.json); `publish --if-newer` rebuilds a page only when the list's revision is newer, and `publish --all-rooms --if-newer` does that in every room with an entry, never creating a page. Owner edits are kept; the v2 marker keeps adoption and recovery safe across versions. Rebased on main: keeps #5980's room-routed commons_env and #6001's s.space title. The history and review rounds for this code are on #5839 (squashed here). Stand: Blue (@chi-chi-air-blue.agent:s.space) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 30, 2026
sonichi
added a commit
that referenced
this pull request
Oct 30, 2026
…eature list and rebuilds when the list is newer Split out of #5839, which narrows to the on_launch launch-hook capability. The tour content is data (skills/tutorial/feature-list.json); `publish --if-newer` rebuilds a page only when the list's revision is newer, and `publish --all-rooms --if-newer` does that in every room with an entry, never creating a page. Owner edits are kept; the v2 marker keeps adoption and recovery safe across versions. Rebased on main: keeps #5980's room-routed commons_env and #6001's s.space title. The history and review rounds for this code are on #5839 (squashed here). Stand: Blue (@chi-chi-air-blue.agent:s.space) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sonichi
added a commit
that referenced
this pull request
Oct 30, 2026
…entity on the room's own connection (#6045) * fix(tutorial): the ownership tag uses the identity on the room's own connection On a two-connection install, `publish` routed the room call to the room's server (#5980) but tagged and recognised pages with the primary channel's AGENT_MXID. A page the agent made on the other server was then not its own, and a plain publish created a second Getting started page. connections.room_agent_mxid (exposed as channel_credentials.room_agent_mxid beside room_env) names the identity on the row connection_for_room picks: the row's recorded agent_mxid, else AGENT_MXID in that row's channel env file, kept only when it names that server. getting_started.main() uses it (after --agent-mxid), and refuses when the routed connection names none rather than use another server's identity. Closes #6044 Stand: Blue (@chi-chi-air-blue.agent:s.space) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(tutorial): a page an earlier run tagged with the primary identity is still this agent's Pages the unrouted code made carry the primary channel's identity in their tag while listed by the room's own identity; with the routed identity alone they still read as not this agent's, and a plain publish would create a second page. owned() also accepts this agent's other-connection id as the tag, still only when the lister is this agent (or unnamed). Refs #6044 Stand: Blue (@chi-chi-air-blue.agent:s.space) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(connections): name the swallowed error when a channel env file cannot be read _launch_file_value and _channel_dir_value still answer '' (fail closed), and now log the exception type at debug level; the test pins both lines. Refs #6044 Stand: Blue (@chi-chi-air-blue.agent:s.space) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
sonichi
added a commit
that referenced
this pull request
Oct 30, 2026
…eature list and rebuilds when the list is newer Split out of #5839, which narrows to the on_launch launch-hook capability. The tour content is data (skills/tutorial/feature-list.json); `publish --if-newer` rebuilds a page only when the list's revision is newer, and `publish --all-rooms --if-newer` does that in every room with an entry, never creating a page. Owner edits are kept; the v2 marker keeps adoption and recovery safe across versions. Rebased on main: keeps #5980's room-routed commons_env and #6001's s.space title. The history and review rounds for this code are on #5839 (squashed here). Stand: Blue (@chi-chi-air-blue.agent:s.space) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sonichi
added a commit
that referenced
this pull request
Oct 30, 2026
…eature list and rebuilds when the list is newer (#6042) * feat(tutorial): the Getting started tour renders from a per-release feature list and rebuilds when the list is newer Split out of #5839, which narrows to the on_launch launch-hook capability. The tour content is data (skills/tutorial/feature-list.json); `publish --if-newer` rebuilds a page only when the list's revision is newer, and `publish --all-rooms --if-newer` does that in every room with an entry, never creating a page. Owner edits are kept; the v2 marker keeps adoption and recovery safe across versions. Rebased on main: keeps #5980's room-routed commons_env and #6001's s.space title. The history and review rounds for this code are on #5839 (squashed here). Stand: Blue (@chi-chi-air-blue.agent:s.space) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(tutorial): pin previous.audience for creating, pending and updating The A.9 audience test built `previous` only under `creating`, so a model that turned a present-invalid previous.audience into "absent" passed for `pending` (all five invalid values) and `updating` (four of five). Each state that carries a previous now gets the full value matrix, and an accepted value is checked as carried into the converted entry. Stand: Blue (@chi-chi-air-blue.agent:s.space) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(tutorial): the manifest description names the refresh paths this PR has Without #5839's on_launch nothing refreshes the page on launch: it is refreshed on re-link, reinstall or /tutorial, and --if-newer gates it on the list's revision. Stand: Blue (@chi-chi-air-blue.agent:s.space) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <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.
Priority
Level: medium
Reason: completes the multi-server routing of #5977; without it these clients still post with whatever token the process environment holds.
Closes
Follows #5977 and #5978 (both on main);
origin/mainis merged in, base ismain.Summary
Every gateway client outside
room_ops/notifynow asksconnections.room_credentialwhich connection to use: the room's own server (exact domain match, so adev.s.spaceroom never goes tos.space), the primary when there is no room. A room no connection serves is refused by name, never answered with another server's token. Installs with one connection keep their existing credential chains (the resolver answersNone).src/owner_room_access.py,src/channel_credentials.pychannel_credentials.room_env(url_vars, token_vars, room): the connection'surl|secretin the capability's first token variable, every other credential variable blanked.resolve_credentials(cap, collab_url, room)uses it unless--collab-urlis explicit.owner_routingreads the primary row'sowner-routing.<instance>.json, falling back to the unsuffixed file.connections.primary_rowis a read-only primary lookup (no import, no vault).crons_table.pyresolve_credentials.ensure-cron-room.py,cron-notify.pyresolve_token(repo, room)asks the resolver first (room creation uses the primary; a cron-room post uses the room's server). A refusal skips creation / returns "not delivered" with the reason on stderr.bee_actions.py_gateway_creds(room);post-roomresolves for the Bee room,register-roomfor the primary; a refusal exits 2.health-check.py --notify-gatewayREMOTE_ALERT_ROOMis read from the primary's channel dir first (legacy dir as fallback), and_gateway_creds(room=...)routes by that room. The status readers are untouched.skills/tutorial/scripts/getting_started.pycommons_env(script, room=...)usesroom_env;connect_actionsapplies blanked variables so an outer token cannot shadow the routed one.src/result_markers.py+ vendored copy)channel-env.sh <dir> --room <room>, so the file loaded is the room's own connection's.room_ops.pyitself already resolves by room from env, channel file or vault. Golden fixture and requeue test updated.room_opsverbs without a roomrooms,history,agents,resolve,events list|pull|streamact through the primary (help text, SKILL.md,docs/connections.md).Before / after evidence
New suite
tests/gateway-clients-room-routing.test.py(three connections: ag2.space legacy, s.space primary, dev.s.space; the real resolver end to end; the process'sREMOTE_TASK_TOKENis ag2.space's, so a client that skips the resolver gets the wrong server).At the parent (
04d88fb32, the test files copied in):(The two passing cases are the vendored-copy parity check and the
room_opsno-room verbs, which #5977 already routes to the primary.)At this head:
Test plan
tests/gateway-clients-room-routing.test.py(new), plusroom-ops-room-connection,connections-room-routing,channel-env-resolve,channel-env-order-contract,connections,cron-notify,bee-actions,health-check-notify-gateway,tutorial-getting-started,pending-questions-room-db,pending-questions-room-db-serve-process,pending-questions-single-store,pending-questions-send,ag2space-links,requeue-prelude-rebuild,gateway-task-envelope-golden,result-markers-golden,bridge-marker-no-leak,delivery-primitives-single-owner,codex-core-launcher,hermetic-env-structural,tests/skills/schedule-crons/crons-table.test.py,skills/agent-room-ops/test_room_ops.py: all pass.ruff checkon changed files: clean.scripts/lint-hermetic-bridge-tests.py: ok.scripts/review-checks.shon the diff: PASS.sys.path.insertinensure-cron-room.pywhen the coresrc/is not yet on the path).Left over
pending_questions_room_db._db_link(a display link shown to the owner) still builds its URL from the capability's own order, not the room's connection.mention's handle lookup uses the primary's/v1/agentsdirectory while it posts through the room's connection; unchanged here.Checklist
gh pr list --state open)🤖 Generated with Claude Code