Skip to content

Retiring a Bot leaves its group Session schedules armed: the standup still fires in the room #1816

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 24ac833d04ba4161b97443586fd01b17
Branch: dev/4-group-session-pause
Base: 46327e8
At: 2026-10-30T08:46:21Z

What happens

Retiring a Bot disarms only the Sessions its own chat lives in. The Sessions it speaks in as a group member stay armed, so a retired Bot keeps waking up in those rooms and runs a turn there every day.

Live main 46327e82, core/src/host/schedules.js:494 — pauseBotSchedules(botId) builds its set as

const parts = new Set([current, ...(rt.botOf(botId)?.segments ?? []).map(segment => segment.sessionId)])

— the current part plus the earlier parts of bot.segments. Missing from it: the Bot's seat in a group (room.sessions[botId]) and the earlier parts of that seat (room.segments[botId]). A row armed on either is never paused.

This is the shape #1799 was filed to remove, back under another face: #1806 (on #1799) closed it for the Bot's own chat and its parts, and the doc sentence it wrote — "Archiving also disarms the target's own chat, every part of it" (docs/subsystems/core-host.md) — describes exactly the limit that leaves this hole open.

Why the group seats are the Bot's, not someone else's

Not an inference, it is the product's own wording:

  • bot_schedules' description (schedules.js:604) counts "its own chat, the earlier parts of it, the group chats it speaks in" as this Bot's Sessions, and placeOf (schedules.js:585) answers a task on a room Session with group chat "<name>" for that Bot.
  • Deletion already treats them as the Bot's: roster.deleteBot (roster.js:568-574) records every room.sessions[id] and room.segments[id] part in state.retiredSessions so the seats keep their owner (#1453). Retirement writes a subset of what deletion writes, and the group seats are the part it leaves out.

Reproduction

Reproduced twice, independently — once as a probe over a real host on an earlier base, once as the failing case on live main 46327e82, which is the case that will land with the fix:

  • Fixture: a room Launch holding Bot Coder (b3) as a member, r1.sessions.b3 = 's-b3'; one daily armed on s-b3 and one armed on the Bot's own chat b3; a second room's own task (r1) and another member's seat (s-b2) armed as the guard.
  • archive_bot { bot: 'Coder' } → Archived Coder. … Paused 1 task(s) in its own chat.
  • The group Session is untouched: the row on s-b3 still reads {"id":"schedule-1","title":"Group standup","status":"active"}, paused is ["b3"], and bot_schedules read from inside s-b3 still lists it — "Group check-in" (schedule-1) — next 2030-01-01T01:00:00.000Z, active, group chat "Launch", session s-b3.

Why it actually runs: the row is still in DSH's active catalog, so it fires into s-b3. Nothing on the host refuses a turn that a scheduled task starts — hooks.js's gate covers an inbound send (request.to), resume.js's covers a restart, and a group round is drawn from roomBots, which a scheduled task does not go through.

Expected

Retiring a Bot disarms every Session it can be woken in: the current part, bot.segments, every room.sessions[botId], and every part in room.segments[botId]. The count and the answer cover all of them (Paused 2 task(s) across its chats. in the fixture above).

A reminder the group set for itself is not the Bot's: a row whose Session is room.id (placeOf's rt.roomOf branch) stays active. That is the whole room's task, not this Bot's, and disarming it because one member retired would be a worse regression than the one this fixes — the red test carries that assertion from the start (in the fixture, r1 keeps active after the archive, and so does another member's s-b2).

Scope

  • core/src/host/schedules.js — collect the group seats into the same set pauseBotSchedules already builds: for each room where room.sessions?.[botId] is set, that Session and each room.segments?.[botId] part. room.id itself is never collected.
  • core/src/host/tools.js:412 — the answer says "in its own chat", which stops being true once the count covers the seats; reworded. The two existing assertions that read it match /Paused 2 task\(s\)/ (retired-bot-paths.test.js, #1709 (5d) and #1799), so none of them changes.
  • core/test/retired-bot-paths.test.js — one case beside #1799's, red first on the base of the PR: before the fix the group row is still active and paused is ["b3"]; after it the row is gone and paused is ["b3","s-b3"], with the guard rows still active.
  • docs/subsystems/core-host.md — the retirement paragraph ("Archiving also disarms the target's own chat, every part of it") gains the group seats and names the one Session it does not touch.
  • regenerate patches/ds-bot/0001-core-fixes.patch.

Duplicate check, run before filing: grep on live main for pauseBotSchedules / room.sessions / retiredSessions / groupOwners, plus closed-issue search for group session schedule retire, pause schedules archive_bot, retiredSessions, disarm schedules, bot_schedules group → #1799, #1709, #1445, #628. Every one of them is about the current part or the Bot's own chat; no issue covers the group seats.

Activity

  1. added a commit that references this issue on Oct 30, 2026
    33a5d46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions