Skip to content

Pool members' cron reports, integration channel snapshots and child-session status are silently dropped #1027

Description

@zfy0701

WS report handlers silently drop a pool member's reports: the repository query joins on agent.daemonId.

What is wrong

Three handlers admit a frame only if the reporting daemon is the agent's recorded daemon. For a pool agent that column is NULL, so the frame is discarded — without an error, because each of these paths treats "not mine" as "ignore".

Cron reports. persistence/repositories/cron.repo.ts:225:

const cron = await this.db.cronDef.findFirst({
  where: { id: cronId, agent: { daemonId: reportingDaemonId } },})
if (!cron) return false

and ws/handlers/cron-report.ts:24 ignores the false. Every fire / progress / completion report from a pool member is dropped, so lastRunAt never advances and the console's run history for a pool agent's crons stays empty forever. Note the sibling read was already compensated: listForDaemon (cron.repo.ts:203, same join) is unioned with listForAgents(heldAgentIds) in orchestrator/placement.ts:401. recordReport was not.

Integration channel snapshots. ws/handlers/integration-channels.ts:28 and :46:

const owned = await deps.integration.activeForDaemon(DaemonId(conn.daemonId))
const integration = owned.find((i) => i.id === p.integrationId)
if (!integration) return   // drops silently

integration.repo.ts:533 joins agent: { daemonId }. integration.repo.ts:542 (activeForAgents) exists precisely as the duty half and is used in the reconcile path (orchestrator/placement.ts:404), but this handler was never converted. Slack channel-membership snapshots from a pool member are discarded, so the console's conversation lists — and the gating defaults and collab-route refresh computed from them — never update.

Cross-daemon child session status. ws/handlers/child-session-status.ts fails on both legs:

:46  if (!parent || parent.daemonId !== conn.daemonId || ) { NOT_FOUND }
:54  if (!childAgent || childAgent.orgId !== parent.orgId || !childAgent.daemonId) { NOT_FOUND }
:62  if (childAgent.daemonId === conn.daemonId) { NOT_FOUND }
:67  const owner = deps.connReg.get(childAgent.daemonId)

The parent proof compares the session's recorded daemon against the asker, so after a rollout the member now serving the parent is not the one that first reported it (and the column is NULL once the retired row is reaped, SessionMeta.daemonId is onDelete: SetNull). The child leg refuses every pool agent outright, because a pool agent names no machine. viewSessionStatus on a cross-daemon child therefore returns "not found" for all pool agents.

Smallest correct fix

Each of these is one predicate:

  • cron: fence on "the reporting daemon serves this cron's agent" (placement ∪ live duty holders) instead of the join;
  • integration channels: activeForDaemon(d) ∪ activeForAgents(servedAgents(d)), the union the reconcile path already builds;
  • child status: authorize the parent with the resolver's may-act predicate, and target the child with servingDaemon(childAgent).

Part of the "authorize on agent.daemonId" recurrence #955 records under M4; siblings #996, #999.

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