You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Multi-account credentials per provider family: id convention + import sources (custody side of insula#1 GAP-1/2) #2
Hit this while provisioning the vault on our Linux box (context: insula#1, filed by us — GAP-1/GAP-2 there cover the consumption side; this issue is the custody side).
Problem
There is no way to seal a second account in the same provider family, and no id convention for one if there were.
Concretely, on this box:
anthropic: two OAuth accounts (main + the fallback account). ck auth import --source opencode --provider anthropic reads opencode's auth.json, which only carries the currently-active account. The fallback account lives in the harness auth plugin's own state file (anthropic-auth.json, accounts array keyed by account uuid) — no import source reads it.
openai: three Codex accounts, same shape — auth.json has the active one, the other two live in the openai-auth plugin's sidebar state.
antigravity: one paid + three free accounts in antigravity-accounts.json. The free accounts' tokens rotate constantly under the plugin's own rotation logic, so a vault snapshot of them goes stale in hours — these probably should NOT be sealed until the vault is the refresher, which makes the id question sharper for the paid+free split.
We stopped at one credential per family (oauth:anthropic, chatgpt:openai, antigravity:google) rather than inventing an id scheme (oauth:anthropic:<uuid>?) that triage here might contradict.
Asks
Is a multi-account id convention planned? If you name one (suffix, separate namespace, whatever), we'll adopt it immediately and report back from a real multi-account box.
Should import grow sources for harness multi-account state files, or is the intended path "run ck auth login once per account" (which mints vault-native credentials and sidesteps the import question)?
Related: the consumer side (how insula's family map would address a second account) is GAP-1/GAP-2 in insula#1 — answering it in either repo likely answers both.
Dual-custody note
Until the vault is the sole refresher, every sealed OAuth credential has two independent refreshers (harness auth plugin + vault adapter) racing on the same refresh token. For our five primaries that risk is worth it because insula consumes them; for fallback accounts there is no consumer yet, so we're deliberately holding them out. If there's a recommended posture here (e.g. seal-but-disable-refresh), the runbook is where we'd expect it.
Dual-custody datum upgraded from anecdote to deterministic: three occurrences now, and the trigger is identified — every harness restart re-authenticates the Anthropic OAuth account (token rotation), which invalidates the vault's sealed snapshot and flips it to needs_reauth. On this box that means the vault copy of an actively-refreshed OAuth credential goes stale on EVERY restart of the harness that owns the live refresher, and the day-N re-seal (module stop → import --replace → start) is now routine maintenance rather than exception handling.
This strengthens the case for whichever direction triage picks: either the vault becomes the sole refresher (harness reads through handles, snapshot never stales) or import grows a watch/re-import mode for externally-refreshed credentials. Happy to test either on this deployment — we hit the path several times a day.
Trigger-analysis data from three restart events since the last comment, because they discriminate between the two candidate mechanisms:
2026-08-10 14:42 — daemon OOM-kill + auto-restart, ~41h since last re-seal: oauth:anthropic came back needs_reauth. Re-sealed.
2026-08-10 14:56 — full box reboot, 14 minutes after that re-seal: came back clean, no re-seal needed. (Two opencode-harness restarts the same evening: also clean.)
2026-08-12 ~15:30 — module drain-restart (binary swap), ~2 days since last re-seal: needs_reauth again. Re-sealed.
So the discriminator held: restart class doesn't matter — elapsed harness re-auths since the last import do. A restart 14 minutes after a fresh import is clean regardless of being a full reboot; a restart days later surfaces staleness regardless of being a gentle drain. The restart isn't the trigger, it's the reveal — the vault copy went stale at whatever moment opencode last rotated the refresh token, and the module only notices when it next constructs the credential.
Operationally that means the re-seal cadence is set by the harness's rotation frequency, not by daemon lifecycle events, and a scheduled re-import (or the fetch-on-miss the issue proposes) would close it independent of restart hygiene.
The id convention exists and is shipped — you stopped one step short of something already supported, so please go ahead.
The grammar is <method>:<provider>[:<label>], parsed in credentials-core/src/credential_id.rs. The label is a free operator-chosen string, not a uuid. Method is what selects the refresh adapter, which is why it leads.
Three Anthropic accounts and two ChatGPT ones side by side, plus an OpenAI API key that does not collide with the OAuth entries because the method differs. apikey:openai and chatgpt:openai are different credentials with different adapters.
A label is worth choosing for a human, not for uniqueness — oauth:anthropic:work reads better in ck auth list and in an audit row than an account uuid, and the vault never needs the id to match the provider's own identifier.
Two things in your report that are correct and are not solved by the convention:
Import sources only read what the harness exposes.--source opencode reads auth.json, which carries the active account only. The fallback accounts living in plugin-private state files have no import source, and you are right that this is a gap in the import path rather than in the id scheme.
For accounts you can obtain interactively, ck auth login --provider anthropic --id oauth:anthropic:work mints an independent credential directly, with no import source needed and no plugin state to read.
Your trigger analysis predates my earlier reply and I had not read it when I wrote that — worth saying, because your discriminator is better evidence than the argument I made, and it independently reaches the same place.
"The restart isn't the trigger, it's the reveal" is exactly right, and the three-event table proves it in the only way that counts: a full reboot 14 minutes after a fresh import came back clean, while a gentle drain-restart two days later did not. Restart class varied and outcome did not; elapsed harness re-auths varied and outcome followed. That is a controlled comparison, not an anecdote, and it rules out the mechanism most people would have assumed.
It also means the re-seal cadence is set by the harness's rotation frequency — which is not a number you control, and it is why the treadmill has no stable maintenance interval. Four occurrences in 24h on your box, per your note on #4.
Where it lands: your conclusion offered two directions, and the first is already built. ck auth login makes the vault the sole refresher, so the snapshot cannot stale because there is no snapshot. The second (watch/re-import for externally-refreshed credentials) is the one I declined on #4, for the reason you accepted there: it keeps a violation of the one-refresher invariant fresh rather than removing it.
The gap your data does leave open is the one this issue actually filed: import sources only read what the harness exposes. For an account you can log into interactively, native login sidesteps it entirely. For one you cannot — no interactive path, credential only obtainable from a plugin's private state — import remains the only door, and that door reads one account per family. That is a real limitation, unaddressed, and it is separate from the staleness treadmill your restart data characterises.
If you hit a provider in that category during your migration wave, say which one. A concrete case is worth more than the general shape, and it would tell me whether the missing piece is more import sources or a way to hand the vault a credential the harness already holds.
Update: the ID convention is shipped as <method>:<provider>[:<account>] (credential_id.rs), and record identity landed in fa4cec4 (PR #30). The remaining open part is import of plugin-private fallback-account state, which is now taking shape as a writer-side ck auth migrate-plugin verb (preserved at tag followup/manifest-lock-and-exports on the fork, to follow PR #28).
@ualtinok, following up on this one. It has been quiet since my 09-04 update, and I think the direction note on #5 has overtaken it.
On your question about the concrete case: there is one, and it didn't need a new import source. On 09-18 two OpenAI fallback accounts that existed only in the openai-auth plugin's private state went into the vault as chatgpt:openai:acct-a (audit seq 1639) and chatgpt:openai:<label> (seq 1640). I staged each account's bare {refresh, access, expires} entry to tmpfs and ran ck auth import without --provider, passing --account-id so the identity check ran against the token's claim. Both have served since then. oauth:anthropic:<label> came in the same way on 08-31, and was later replaced by a real ck auth login on 09-22 after it latched.
So the gap this issue was left open for (plugin-private fallback accounts) has been handled by hand-staging plus import. The migrate-plugin verb I mentioned in the last update is superseded by #5: the OpenCode verbs collapse into ck auth import, and the vault stops writing consumer files.
Unless you want this open for a different import source, I'd close it in favour of #5. Is that fine with you?
The ID convention (<method>:<provider>[:<account>]) and record identity shipped, and the plugin-private fallback accounts went in through ck auth import with --account-id, so no new import source was needed. The vault serves several accounts per provider today: five Claude accounts and two ChatGPT accounts.
Activity
iceteaSA commentedon Aug 9, 2026
Dual-custody datum upgraded from anecdote to deterministic: three occurrences now, and the trigger is identified — every harness restart re-authenticates the Anthropic OAuth account (token rotation), which invalidates the vault's sealed snapshot and flips it to needs_reauth. On this box that means the vault copy of an actively-refreshed OAuth credential goes stale on EVERY restart of the harness that owns the live refresher, and the day-N re-seal (module stop → import --replace → start) is now routine maintenance rather than exception handling.
This strengthens the case for whichever direction triage picks: either the vault becomes the sole refresher (harness reads through handles, snapshot never stales) or import grows a watch/re-import mode for externally-refreshed credentials. Happy to test either on this deployment — we hit the path several times a day.
iceteaSA commentedon Aug 12, 2026
Trigger-analysis data from three restart events since the last comment, because they discriminate between the two candidate mechanisms:
oauth:anthropiccame backneeds_reauth. Re-sealed.needs_reauthagain. Re-sealed.So the discriminator held: restart class doesn't matter — elapsed harness re-auths since the last import do. A restart 14 minutes after a fresh import is clean regardless of being a full reboot; a restart days later surfaces staleness regardless of being a gentle drain. The restart isn't the trigger, it's the reveal — the vault copy went stale at whatever moment opencode last rotated the refresh token, and the module only notices when it next constructs the credential.
Operationally that means the re-seal cadence is set by the harness's rotation frequency, not by daemon lifecycle events, and a scheduled re-import (or the fetch-on-miss the issue proposes) would close it independent of restart hygiene.
ualtinok commentedon Aug 15, 2026
The id convention exists and is shipped — you stopped one step short of something already supported, so please go ahead.
The grammar is
<method>:<provider>[:<label>], parsed incredentials-core/src/credential_id.rs. The label is a free operator-chosen string, not a uuid. Method is what selects the refresh adapter, which is why it leads.Live inventory on my own vault right now:
Three Anthropic accounts and two ChatGPT ones side by side, plus an OpenAI API key that does not collide with the OAuth entries because the method differs.
apikey:openaiandchatgpt:openaiare different credentials with different adapters.A label is worth choosing for a human, not for uniqueness —
oauth:anthropic:workreads better inck auth listand in an audit row than an account uuid, and the vault never needs the id to match the provider's own identifier.Two things in your report that are correct and are not solved by the convention:
Import sources only read what the harness exposes.
--source opencodereadsauth.json, which carries the active account only. The fallback accounts living in plugin-private state files have no import source, and you are right that this is a gap in the import path rather than in the id scheme.You are right to hold off on the antigravity free accounts. Sealing a credential whose tokens rotate under someone else's control produces a snapshot that goes stale in hours — that is dual custody, and the staleness is the symptom. See my reply on Vault-as-mirror: re-import from the registered source on change, so a rotating harness credential stops requiring manual re-seals #4: the fix is not a better mirror, it is not having one.
For accounts you can obtain interactively,
ck auth login --provider anthropic --id oauth:anthropic:workmints an independent credential directly, with no import source needed and no plugin state to read.ualtinok commentedon Aug 15, 2026
Your trigger analysis predates my earlier reply and I had not read it when I wrote that — worth saying, because your discriminator is better evidence than the argument I made, and it independently reaches the same place.
"The restart isn't the trigger, it's the reveal" is exactly right, and the three-event table proves it in the only way that counts: a full reboot 14 minutes after a fresh import came back clean, while a gentle drain-restart two days later did not. Restart class varied and outcome did not; elapsed harness re-auths varied and outcome followed. That is a controlled comparison, not an anecdote, and it rules out the mechanism most people would have assumed.
It also means the re-seal cadence is set by the harness's rotation frequency — which is not a number you control, and it is why the treadmill has no stable maintenance interval. Four occurrences in 24h on your box, per your note on #4.
Where it lands: your conclusion offered two directions, and the first is already built.
ck auth loginmakes the vault the sole refresher, so the snapshot cannot stale because there is no snapshot. The second (watch/re-import for externally-refreshed credentials) is the one I declined on #4, for the reason you accepted there: it keeps a violation of the one-refresher invariant fresh rather than removing it.The gap your data does leave open is the one this issue actually filed: import sources only read what the harness exposes. For an account you can log into interactively, native login sidesteps it entirely. For one you cannot — no interactive path, credential only obtainable from a plugin's private state — import remains the only door, and that door reads one account per family. That is a real limitation, unaddressed, and it is separate from the staleness treadmill your restart data characterises.
If you hit a provider in that category during your migration wave, say which one. A concrete case is worth more than the general shape, and it would tell me whether the missing piece is more import sources or a way to hand the vault a credential the harness already holds.
iceteaSA commentedon Sep 4, 2026
Update: the ID convention is shipped as
<method>:<provider>[:<account>](credential_id.rs), and record identity landed in fa4cec4 (PR #30). The remaining open part is import of plugin-private fallback-account state, which is now taking shape as a writer-sideck auth migrate-pluginverb (preserved at tag followup/manifest-lock-and-exports on the fork, to follow PR #28).iceteaSA commentedon Sep 23, 2026
@ualtinok, following up on this one. It has been quiet since my 09-04 update, and I think the direction note on #5 has overtaken it.
On your question about the concrete case: there is one, and it didn't need a new import source. On 09-18 two OpenAI fallback accounts that existed only in the openai-auth plugin's private state went into the vault as
chatgpt:openai:acct-a(audit seq 1639) andchatgpt:openai:<label>(seq 1640). I staged each account's bare{refresh, access, expires}entry to tmpfs and ranck auth importwithout--provider, passing--account-idso the identity check ran against the token's claim. Both have served since then.oauth:anthropic:<label>came in the same way on 08-31, and was later replaced by a realck auth loginon 09-22 after it latched.So the gap this issue was left open for (plugin-private fallback accounts) has been handled by hand-staging plus
import. Themigrate-pluginverb I mentioned in the last update is superseded by #5: the OpenCode verbs collapse intock auth import, and the vault stops writing consumer files.Unless you want this open for a different import source, I'd close it in favour of #5. Is that fine with you?
ckcred-alfonso commentedon Oct 5, 2026
Closing in favour of #5, as you suggested.
The ID convention (
<method>:<provider>[:<account>]) and record identity shipped, and the plugin-private fallback accounts went in throughck auth importwith--account-id, so no new import source was needed. The vault serves several accounts per provider today: five Claude accounts and two ChatGPT accounts.Thanks for carrying this one through.