Static api-key providers have no vault lane, so migrating their keys into custody breaks their quota reading
Observed today on this host. The credential vault's migrate-opencode moved two static keys out of ~/.local/share/opencode/auth.json into custody, leaving a pointer string in the key field. Insula's quota lanes for those providers sent the pointer as the bearer and got a permanent 401:
synthetic credential_rejected HTTP 401 18:45:34Z → 19:14:42Z
deepseek credential_rejected HTTP 401 18:55:47Z → 19:14:50Z
Both recovered on the first fetch after the keys were restored to the file. Model traffic through the vault was unaffected throughout — only insula's reading of the quota was.
Why there was no fallback
deepseek.rs:176–181 and synthetic.rs:526 resolve the key as env var → opencode_auth::read_provider(...), and stop there. vault_handles.rs providers_for_id maps chatgpt:openai, oauth:anthropic, oauth:xai, oauth:google, antigravity:google, kimi-for-coding and the cookie: families; an apikey:<provider> id falls to "ignored ids outside supported vault mapping". So a vault handle for apikey:deepseek is accepted by the file loader and does nothing. (Two such handles existed here until 2026-08-26 and were revoked on exactly that finding — insula could not consume them.)
The consequence is that for api-key providers, custody and quota reading are mutually exclusive today: the key is readable by insula or it is in the vault, not both.
Ask
An apikey:<provider> vault family, served through the same CredentialSource path the OAuth lanes use — credential.get returning the key as the payload, report_auth_failure on 401 as today. Loader side is the mapping arm plus the provider's handles() preferring a vault handle over the local read when one is configured, which is the precedence the cookie lane already expresses (cookie_vault.rs handles: an explicit deposit takes the provider vault-only). Static keys do not rotate, so none of the identity/flux machinery is exercised; account_id can stay None for a single-key provider.
Not asking for the migration tool to know about insula — the fix belongs on the consumer that reads the file.
Base: insula d2f065a. Line numbers against it.
Static api-key providers have no vault lane, so migrating their keys into custody breaks their quota reading
Observed today on this host. The credential vault's
migrate-opencodemoved two static keys out of~/.local/share/opencode/auth.jsoninto custody, leaving a pointer string in thekeyfield. Insula's quota lanes for those providers sent the pointer as the bearer and got a permanent 401:Both recovered on the first fetch after the keys were restored to the file. Model traffic through the vault was unaffected throughout — only insula's reading of the quota was.
Why there was no fallback
deepseek.rs:176–181andsynthetic.rs:526resolve the key as env var →opencode_auth::read_provider(...), and stop there.vault_handles.rs providers_for_idmapschatgpt:openai,oauth:anthropic,oauth:xai,oauth:google,antigravity:google,kimi-for-codingand thecookie:families; anapikey:<provider>id falls to "ignored ids outside supported vault mapping". So a vault handle forapikey:deepseekis accepted by the file loader and does nothing. (Two such handles existed here until 2026-08-26 and were revoked on exactly that finding — insula could not consume them.)The consequence is that for api-key providers, custody and quota reading are mutually exclusive today: the key is readable by insula or it is in the vault, not both.
Ask
An
apikey:<provider>vault family, served through the sameCredentialSourcepath the OAuth lanes use —credential.getreturning the key as the payload,report_auth_failureon 401 as today. Loader side is the mapping arm plus the provider'shandles()preferring a vault handle over the local read when one is configured, which is the precedence the cookie lane already expresses (cookie_vault.rs handles: an explicit deposit takes the provider vault-only). Static keys do not rotate, so none of the identity/flux machinery is exercised;account_idcan stayNonefor a single-key provider.Not asking for the migration tool to know about insula — the fix belongs on the consumer that reads the file.
Base: insula
d2f065a. Line numbers against it.