Expand description
On-disk / keychain credential cleanup for a deleted identity account.
Layer 1 of the account-delete auth-lifecycle remediation
(docs/analysis/ANALYSIS_ACCOUNT_DELETE_AUTH_LIFECYCLE_GAP_2026_07_14.md
§4 option 1): deleting an account row must not leave its credential
material behind. Two secret backends carry on-host state:
SecretRef::Keychain— delete the OS-keychain entry (moved here from the inline block in thedeleteidentityaccounthandler).SecretRef::OAuthConfigDir { dir }— the CLI’s live access + refresh tokens sit in that directory; remove the tree, except the provider’s conversation history (ProviderConfig::history_native_subdir, e.g. Claude’sprojects/), which lives in the same directory when auth is shared. History and credentials are separate persistence categories (SPEC_AGENT_IDENTITY_HISTORY_PERSISTENCE_PROTOCOL_2026_08_16.md P1); deleting a login must not delete the conversations (SPEC_ARMORY_ACCOUNTS_DELETE_AND_INLINE_DETAIL_2026_10_04.md H1). This is best-effort and containment-guarded: the dir is only removed when it resolves INSIDE the agentmux identities root (~/.agentmux/shared/identities/). The legacy~/.claudemigration case (and any other ambient/global CLI dir) is the user’s own login — log and skip, never delete.
Other variants (Env, SecretsManager, PlaintextDev) hold no
agentmux-owned on-host state → no-op.
All log lines use the identity.delete: prefix so they land in the
muxlog auth vocabulary (regex identity\.(unlink|delete|self\.|account)),
and use info!/warn! — the production filter is
“agentmuxsrv=info,info”, so debug! would be invisible (reagent P1,
PR #2143). Provider-side token revocation (running the CLI’s own
logout against the dir before deleting) is an explicit follow-up —
it needs per-provider subprocess plumbing this module doesn’t have.
Enums§
- Prune
Outcome 🔒 - Secret
Cleanup - What
cleanup_account_secretsdid, for callers/tests to assert on. Logging already happened inside the function.
Functions§
- cleanup_
account_ secrets - Best-effort removal of the on-host credential material behind
acct.secret_ref. Never returnsErr— account deletion must not be blocked by cleanup trouble; every outcome is logged. - cleanup_
oauth_ 🔒dir - history_
subdir_ 🔒for - The conversation-history subdirectory name for an OAuth config dir:
from the account’s provider, or else from the dir’s own name, which is
the provider’s
auth_dir_name(identities/<account>/<auth_dir_name>/). - history_
subdir_ 🔒for_ auth_ dir - prune_
orphan_ 🔒keeping_ history - Removes an orphaned account dir (
<root>/<account_id>/), only if it resolves strictly insideroot(the same escape guardcleanup_oauth_diruses), except that each provider subdir’s conversation history is kept, the same rule account delete follows (remove_credentials_keep_history). Without this, the sweep would delete, a few minutes later, the history that deleting the account kept. - remove_
credentials_ 🔒keep_ history - Remove
dir, keeping itshistorychild when that child is a real, non-empty directory. Returns the kept path, orNonewhen the whole tree was removed. - sweep_
orphaned_ account_ dirs - Sweep
identities_root’s direct children for orphaned per-account dirs:compute_and_ensure_account_dirmints a fresh<account_id>/dir and writes real credential files into it BEFORE the login that dir belongs to ever completes, so noIdentityAccountrow exists yet to reference it. A login that’s abandoned, times out, or crashes the app mid-flow leaves that directory behind forever — reagent’s finding on #2260: “abandoned/failed attempts leave orphaned directories with no cleanup path.”