fix(cli): hydrate receiver inbound.db on approval-path destinations add#2510
Open
glifocat wants to merge 2 commits into
Open
fix(cli): hydrate receiver inbound.db on approval-path destinations add#2510glifocat wants to merge 2 commits into
glifocat wants to merge 2 commits into
Conversation
…dd/remove
The `destinations add` and `destinations remove` custom ops in the admin
CLI INSERT/DELETE rows in the central `agent_destinations` table, but
did not project the change into running sessions' `inbound.db`. The
agent-runner container reads its destination map from the per-session
projection, so until the next container spawn (`container-runner.ts`
hydrates on every wake), the running agent saw a stale map — explaining
the "dropped: unknown destination" symptom after a fresh `ncl
destinations add` even though the central row was clearly committed.
Same handler runs for both the direct-host path and the approval-execution
path because the `cli_command` approval handler in `dispatch.ts` re-enters
`dispatch()` as `caller: 'host'`, so the fix at the handler level covers
both surfaces.
Helper iterates over `getSessionsByAgentGroup(agentGroupId)` (every
active session for the affected agent), guarded by `hasTable('agent_destinations')`
and a lazy dynamic import of `writeDestinations` to keep the agent-to-agent
module optional. Per-session try/catch keeps one bad session from killing
the whole projection; failures are logged at WARN with session id + error.
Regression test invokes the dispatcher with `caller: 'host'` (the same
re-entry the approval handler uses after admin approves), with two active
sessions on the source agent group, and asserts the `destinations` row
lands in every session's inbound.db after `add` and is cleared after `remove`.
Fixes #2465
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.
Type of Change
.claude/skills/<name>/, no source changes)Description
Fixes #2465 (related: #2464)
The bug
ncl destinations add(and its symmetric counterpartncl destinations remove) require admin approval. When the approval handler executes, it INSERTs (or DELETEs) the row in the centralagent_destinationstable — but it did not project the change into the affected agent's per-sessioninbound.db.The agent-runner reads its destination map from the per-session projection. Until the next container spawn (
container-runner.ts:118-121already hydrates on every wake viawriteDestinations), the running agent sees a stale map, so the freshly-approved destination silently drops withunknown destinationatsend_messagetime. This is the projection-invariant called out at the top ofsrc/modules/agent-to-agent/db/agent-destinations.ts.The fix
A small helper
projectDestinationsToSessions(agentGroupId)lives insrc/cli/resources/destinations.tsand is called after both theaddINSERT and theremoveDELETE. It iteratesgetSessionsByAgentGroup(agentGroupId)and invokeswriteDestinations(agentGroupId, session.id)for each — exactly the pattern container-runner uses on spawn. Failures are caught per-session and surfaced as a WARN log with session id + err (no silent best-effort).The same handler is hit by both the direct-host call path and the approval-execution path, because
dispatch.ts'scli_commandapproval handler re-entersdispatch()withcaller: 'host'and lands on the samecmd.handler(parsed, ctx)line. So the fix at the handler level covers both surfaces.Reviewer call-outs
(a) Earlier "restart doesn't fix it" report attributed to wrong-side restart. A 2026-05-13 thread suggested restarting the agent didn't pick up the new destination. After tracing it:
container-runner.ts:118-121already callswriteDestinationson every container spawn, andspawnContaineris the bottom of both restart branches inncl groups restart(src/cli/resources/groups.ts:62-110). The earlier symptom was consistent with restarting the wrong side (the requester, not the receiver agent) — restarting the receiver agent's container has always rehydrated its inbound.db. No code change required there; flagging here so reviewers don't re-litigate.(b)
removesymmetry is the same bug class. A successfuldestinations removeleft the local_name still resolvable inside the running container — the central row was gone but the projection still had it. The fix callsprojectDestinationsToSessionsafter the DELETE too. Without that, a removed destination becomes a stale grant until the next container spawn.(c) Helper lives in the resource file, not the agent-to-agent module. Two reasons:
src/cli/resources/which is loaded by every host build, and guards onhasTable('agent_destinations')+ a dynamicawait import(...)keeps the module optional.writeDestinationsis the right primitive to live in the agent-to-agent module; the loop over sessions + per-session error handling + log surface are CLI-handler concerns.Verification
pnpm typecheck→ 0 errorspnpm test→ 31 files, 328 tests, all passingdestinations.test.tsinvokesdispatch(..., { caller: 'host' })— identical re-entry path to the approval handler indispatch.ts:181-191— with two active sessions on the source agent group, and asserts the projecteddestinationsrow lands in every session's inbound.db afteradd, and is cleared afterremove. Two sessions catches the common regression shape where a fix only updates the "latest" session.For Skills
(N/A — this is a source-code fix, not a skill.)