Skip to content

fix(tutorial): tag and recognise the Getting started page with the identity on the room's own connection - #6045

Merged
sonichi merged 3 commits into
mainfrom
fix/tutorial-identity-per-room
Oct 30, 2026
Merged

sonichi merged 3 commits into
mainfrom
fix/tutorial-identity-per-room

Conversation

@sonichi

@sonichi sonichi commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Priority

Level: medium
Reason: on a two-connection install, /tutorial or first contact can add a second Getting started page to a room that already has one.

Closes

Closes #6044

Summary

#5980 routes the tutorial's room call to the room's own server, but the identity used for the ownership tag and the ownership check still came from the primary channel's AGENT_MXID. A page the agent made on the other server is listed by that server's identity, so owned() was false, and a plain publish created a second 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 row's server;
    • None on a one-connection install, so behaviour there is unchanged.
  • getting_started.main() uses it, after --agent-mxid. When the routed connection names no identity, it refuses (exit 4) rather than use another server's.
  • Pages the unrouted code already made carry the primary identity in their tag. owned() now also accepts this agent's other-connection id as the tag. The lister check is unchanged and comes first in owned(): if by and by != agent: return False.
    • A page listed by anyone else is never this agent's, under any tag.
    • A page with no by falls through to the marker test, where either this agent's routed id or its alias counts. That keeps main's behaviour for a lister-less page and widens only the accepted tag.
    • test_another_agents_page_is_never_taken_for_an_alias pins three cases: another lister is refused, no lister plus the alias tag is accepted, and no lister without the alias is refused.
  • _launch_file_value and _channel_dir_value still answer '' on any error (fail closed). They now log the exception type at debug level, and the test pins both lines.

This is on main's script. #6042 (the feature list) rewrites the same file. Whichever lands second carries this into it, and #6042 links #6044.

Commits

  • 4e0dcfb4: route the identity.
  • 739f97ba: recognise pages already tagged with the primary identity.
  • 0549b319: debug log naming the error when a channel env file cannot be read.

Before / after evidence

These are the new tests with main's getting_started.py, connections.py and channel_credentials.py. The first test is the live symptom: a second page html-p2.

$ python3 tests/tutorial-getting-started.test.py RoutedIdentityTest; python3 tests/gateway-clients-room-routing.test.py TestRoomAgentMxid   # main's code
ERROR: test_another_agents_page_is_never_taken_for_an_alias
ERROR: test_the_rooms_identity_comes_from_its_own_connection
AttributeError: module 'channel_credentials' has no attribute 'room_agent_mxid'
FAIL: test_a_page_an_earlier_run_tagged_with_the_primary_identity_is_still_this_agents
AssertionError: Tuples differ: (0, 'html-p2') != (0, 'html-p1')
FAIL: test_a_plain_publish_finds_the_page_its_rooms_identity_made_and_never_creates_a_second
AssertionError: 'html-p2' != 'html-p1'
FAIL: test_a_room_whose_connection_names_no_identity_is_refused_not_given_anothers
AssertionError: 0 != 4
Ran 5 tests in 0.406s
FAILED (failures=3, errors=2)
Ran 4 tests in 0.021s
FAILED (errors=4)

At head 0549b319:

$ python3 tests/tutorial-getting-started.test.py
Ran 114 tests in 4.953s
OK (skipped=1)
$ python3 tests/gateway-clients-room-routing.test.py
Ran 35 tests in 0.107s
OK
$ python3 tests/connections-room-routing.test.py
Ran 25 tests in 0.019s
OK
$ git diff origin/main | bash scripts/review-checks.sh
review-checks: PASS (hardcoded-paths + root-artifacts + prose-cap clean)

I ran the touched test files under a line tracer that, like the coverage gate, follows only the main thread. Every changed line is covered: connections.py 22/22, channel_credentials.py 1/1, getting_started.py 9/9.

Checklist

(agent-assisted, Blue for Chi's seat)

🤖 Generated with Claude Code

sonichi and others added 2 commits October 30, 2026 07:06
…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>
… 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>
…nnot 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>
@github-actions

Copy link
Copy Markdown
Contributor

@cla-assistant check

@qingyun-wu qingyun-wu 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.

Reviewed at 0549b31.

Verified:

  • connections.room_agent_mxid uses the same connection_for_room row as room_credential. It returns None on a one-connection install, so main() falls back to agent_mxid() with no aliases, which is unchanged. It returns an identity only when that identity names the row's own server, and returns '' otherwise. main() refuses that case with exit 4 rather than tag a page with another server's id.
  • owned() still checks the lister first, so the alias only widens the accepted tag for a page this agent listed or one with no lister. test_another_agents_page_is_never_taken_for_an_alias pins this.
  • I ran tutorial-getting-started (114 OK), gateway-clients-room-routing (35 OK) and connections-room-routing (25 OK) locally at head.
  • CI: the two red shards are not this diff. pool-restart-deliveries.test.py also fails on a recent main run. whatsapp-guided-connect.test.py failed with an auth/session error and imports none of the touched modules. tsc + tests (clean install) is only the aggregate of those two shards.

Non-blocking:

  1. room_agent_mxid resolves the channel dir and env file in its own way: env var, then launch file, then channel_dir_for, read through device_env and candidates. connection_credential uses only channel_dir_for and resolve_channel_env. The server check stops a wrong-server identity, but the two resolvers can still read different files for the same row. A shared helper would keep them from drifting.
  2. Worst case: on a multi-connection install whose routed row records no agent_mxid and whose channel env file has none, /tutorial now refuses where it used to publish, with the wrong tag. That is the intended fail-closed behaviour. The refusal text names --agent-mxid, but it may be worth a line in the tutorial skill docs.

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Gate

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

Diff Coverage

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

  • skills/tutorial/scripts/getting_started.py (100%)
  • src/channel_credentials.py (100%)
  • src/connections.py (100%)

Summary

  • Total: 52 lines
  • Missing: 0 lines
  • Coverage: 100%

@sonichi
sonichi merged commit c6122fa into main Oct 30, 2026
72 of 75 checks passed
@sonichi
sonichi deleted the fix/tutorial-identity-per-room branch October 30, 2026 14:55
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.

tutorial: on a two-connection install a plain publish creates a second Getting started page (identity not routed by room)

2 participants