Surfaced from a self-review thread on #12582 (packages/core/src/agents/workspace-agents/store.ts, enrollAgentHost).
What happens. Enrollment always mints a fresh hostId/secret and appends (hosts: [...registry.hosts, host]); nothing dedupes by name or workspaceCwd. Re-joining the same machine — which is exactly what happens after a 401, since the Host deletes its credential file (agent-host-client.ts) and needs a new link — leaves the previous row in the registry with a still-valid secretHash, plus a second entry in the roster.
The open design question. Either a re-enrollment replaces the Host (dedupe in the same write), or the old credential deliberately stays valid until DELETE .../hosts/:hostId — in which case the operator-facing surface should say that re-joining does not revoke the previous credential.
Scope of a fix. Decide the dedupe semantics (by name? by workspaceCwd?), implement it in enrollAgentHost, and either revoke the superseded credential in the same write or document that re-joining keeps it valid.
Surfaced from a self-review thread on #12582 (
packages/core/src/agents/workspace-agents/store.ts,enrollAgentHost).What happens. Enrollment always mints a fresh
hostId/secret and appends (hosts: [...registry.hosts, host]); nothing dedupes bynameorworkspaceCwd. Re-joining the same machine — which is exactly what happens after a401, since the Host deletes its credential file (agent-host-client.ts) and needs a new link — leaves the previous row in the registry with a still-validsecretHash, plus a second entry in the roster.The open design question. Either a re-enrollment replaces the Host (dedupe in the same write), or the old credential deliberately stays valid until
DELETE .../hosts/:hostId— in which case the operator-facing surface should say that re-joining does not revoke the previous credential.Scope of a fix. Decide the dedupe semantics (by
name? byworkspaceCwd?), implement it inenrollAgentHost, and either revoke the superseded credential in the same write or document that re-joining keeps it valid.