Skip to content

Face-to-face room cannot be renamed: updateRoom runs the two-Bot check on a name-only patch #1764

Closed

Description

@Birfy

Written-by: Chief

A face-to-face room cannot be renamed. Settings → Group chats → Name → Enter answers the red line “群聊至少要两个 Bot” (A group chat needs at least two Bots) and the name stays what it was. The room in the screenshot has 3 people and 4 Bots, so the message is wrong there, and the rename is refused for a patch that changes no membership at all.

Steps

  1. Open a face-to-face room where each person brought one Bot (the normal shape: 1 local Bot of yours + friend Bots as seats).
  2. Settings → Group chats → the room → Name (“Leave it empty to name the group after its members.”).
  3. Type a name and press Enter.

Expected: the room is renamed, and every member's sidebar/bubble title follows.
Actual: the save is refused with A group chat needs at least two Bots; the name is unchanged. The same refusal hits Notice, Reply mode and Task lead edits, because they all go through the same patch call.

Root cause

core/src/host/roster.js:364 updateRoom(id, patch, actorId, actor) builds the member list and then, at line 378, calls checkMembers(members) for every patch, including one that only sets name:

let members = Array.isArray(patch.members) ? [...new Set(patch.members)] : [...room.members]
for (const member of patch.add ?? []) if (!members.includes(member)) members.push(member)
members = members.filter(member => !(patch.remove ?? []).includes(member) && rt.botOf(member))
checkMembers(members)            // roster.js:378 — also on a rename

checkMembers (roster.js:293) throws below two. room.members is derived at core/src/host/room-membership.js:409-410:

const syncMembers = (room) => {
  room.members = currentParticipants(room).filter(p => p.kind === 'bot' && !p.remote).map(p => p.id)

A face-to-face room's seats come from Bot Social (syncFaceParticipants, room-membership.js:2769) and every friend's Bot is remote: true, so room.members holds only my own Bot: 1 entry → the two-Bot rule fires on a name-only patch.

The two-Bot rule already exempts these rooms where it really guards membership, so this call site is the odd one out:

  • room-membership.js:762 (removing a seat) skips it for origin === 'face-to-face' and inbound-share.
  • roster.js:372 refuses member/admin edits from a room admin in a face-to-face room, because the seats are synced and not patched.
  • The client even documents the rule at core/src/client/group-settings.js:274: “The host checks for two members on every change, even a rename.”

Scope of the fix

  • core/src/host/roster.js updateRoom: run the two-Bot floor only for a patch that changes membership (members/add/remove present), and exempt room.origin === 'face-to-face' / 'inbound-share' exactly as line 372 and room-membership.js:762 do — their seats are synced, not patched, so a name/notice/mode edit must never be judged by them.
  • Keep the upper bound (ROOM_MAX_MEMBERS) and the admin-must-be-a-member rule where they belong.
  • Regression test in core/test/: a face-to-face room with one local Bot and remote seats, renamed through updateRoom({ name }) — keeps the new name; the test must fail without the change (it throws the two-Bot error today). A second case: a normal group whose members drops to one still refuses a membership patch, so the guard is not weakened.

No design question is open: renaming a room is expected to work, and the message the user sees is simply false here.

Notes

  • Seen on the owner's instance flybot682b8a43 (v0.2.15), 2026-10-30.
  • The screenshot shows the red line above the Name field, i.e. the error is rendered by the settings dialog from the host refusal (core/src/client/settings.js:205).

Activity

  1. added
    bugSomething isn't working
    area:uiWeb/desktop UI and copy
    on Oct 30, 2026
  2. Birfy commented on Oct 30, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Triage

    Label: agent-ready (no previous state label to remove).

    Design conflict closed. The two-Bot floor already exempts these rooms where it really guards membership: core/src/host/roster.js:372 refuses member/admin edits in a face-to-face room (its seats are synced, not patched), and core/src/host/room-membership.js:762 skips the floor for face-to-face and inbound-share. room-membership.js:409-410 is why it misfires here: room.members keeps only local, non-remote Bots, so a face-to-face room with one local Bot has members.length === 1, and updateRoom judges a name-only patch by that count. Nothing is left for the owner: renaming a room is expected to work, and the message is false in that room.

    Re-read at main 8252bce9 (all references check out): roster.js:293-296 checkMembers throws below two; roster.js:375-378 builds members and calls checkMembers(members) unconditionally; room-membership.js:409-410 filters kind === 'bot' && !p.remote; room-membership.js:762 and roster.js:372 carry the exemptions; core/src/client/group-settings.js:274 states the rule as "on every change, even a rename".

    No open PR covers this: of the four open PRs, #1727 touches roster.js at :529 (the retire path), none touches updateRoom, and none names #1764.

    Files to change

    • core/src/host/roster.js — updateRoom (:364-378): run the two-Bot floor only for a patch that changes membership (patch.members / add / remove present), and exempt room.origin === 'face-to-face' / 'inbound-share' exactly as :372 and room-membership.js:762 do. Keep ROOM_MAX_MEMBERS and the admin-must-be-a-member rule where they are.
    • core/src/client/group-settings.js:274-275 — the comment says the host checks two members "on every change, even a rename"; after the fix that is no longer true, so reword it in the same change (cd core && npm run build, commit core/client.js).
    • core/test/remote-room.test.js — it already builds a face-to-face room (rt.upsertFaceRoom, FACE_SPEC, call('update-room', …)).
    • docs/subsystems/core-host.md — the two-Bot rule lives on an owned path: state where the floor applies, or carry a valid Docs-not-needed: trailer in both the branch commit and the PR body.
    • One fragment: changelog.d/1764-room-rename-face-to-face.fixed.md.

    Red first — a face-to-face room with one local Bot plus remote seats, then call('update-room', { id: room.id, name: 'Renamed' }): today it throws A group chat needs at least two Bots, so asserting the new name is red at main. Second case, so the guard is not weakened: a normal room whose members drops to one still refuses a membership patch.

    Acceptance

    cd core && node --import ./test/harness-resolve.mjs --test test/remote-room.test.js
    cd core && npm test && cd .. && npm run verify
    
  3. added
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Oct 30, 2026
  4. Birfy commented on Oct 30, 2026

    @Birfy
    CollaboratorAuthor

    Written-by: Dev-3

    Claimed #1764 — branch dev/1764-room-rename-face-to-face; first step: narrow the two-Bot floor in updateRoom to membership patches and exempt synced-seat rooms.

  5. self-assigned this
    on Oct 30, 2026
  6. added
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    area:uiWeb/desktop UI and copy
    bugSomething isn't working
    and removed
    bugSomething isn't working
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    area:uiWeb/desktop UI and copy
    on Oct 30, 2026
  7. removed
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Oct 30, 2026
  8. added a commit that references this issue on Oct 30, 2026
    51f257e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:uiWeb/desktop UI and copybugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions