can1357/oh-my-pi · error · AIError.ValidationError

No credential with id=${id}

Error message

No credential with id=${id}

What it means

Thrown by the auth storage's credential refresh persistence path when the refreshed credential can no longer be found by id. After an async refresh completes, the code re-looks-up the row positionally because the array may have been reordered or shrunk while awaiting; if #replaceCredentialById returns -1, the credential was disabled or removed mid-refresh and there is no live row to persist into, so a ValidationError is raised instead of writing to a stale index.

Source

Thrown at packages/ai/src/auth-storage.ts:6756

				type: "oauth",
				access: refreshed.access,
				refresh: refreshed.refresh,
				expires: refreshed.expires,
				accountId: refreshed.accountId ?? attempted.accountId,
				email: refreshed.email ?? attempted.email,
				projectId: refreshed.projectId ?? attempted.projectId,
				enterpriseUrl: refreshed.enterpriseUrl ?? attempted.enterpriseUrl,
				apiEndpoint: refreshed.apiEndpoint ?? attempted.apiEndpoint,
				orgId: refreshed.orgId ?? attempted.orgId,
				orgName: refreshed.orgName ?? attempted.orgName,
				authorizedAt: refreshed.authorizedAt ?? attempted.authorizedAt,
			};
			// Persist by id: the array may have been reordered/shrunk while the
			// refresh was in flight, so the pre-await positional index is unsafe. A
			// -1 means the row was disabled/removed mid-refresh — surface that as a
			// miss rather than implying a live row the snapshot won't contain.
			if (this.#replaceCredentialById(provider, id, updated) === -1) {
				throw new AIError.ValidationError(`No credential with id=${id}`);
			}
			return {
				id,
				provider,
				credential: { ...updated, refresh: REMOTE_REFRESH_SENTINEL },
				identityKey: resolveCredentialIdentityKey(provider, updated),
			};
		}
		throw new AIError.ValidationError(`No credential with id=${id}`);
	}

	/**
	 * Disable the credential with the given id and emit a
	 * {@link CredentialDisabledEvent}. Used by the auth-broker server to honour
	 * `POST /v1/credential/:id/disable`. Returns `false` when no such row exists.
	 */
	disableCredentialById(id: number, disabledCause: string): boolean {
		for (const [provider, entries] of this.#data) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-fetch the credential list and confirm the id still exists before refreshing
  2. Retry the refresh with a freshly resolved credential id, or re-authenticate to create a new credential
  3. Check whether another tool/process disabled or removed the credential (auth-broker disable endpoint) and restore it
  4. Wrap the refresh call in try-catch for AIError.ValidationError and treat it as 'credential gone' rather than retrying with the same id

Example fix

// before
const updated = await refresh(credential); // may take seconds
storage.replaceByIndex(originalIndex, updated); // stale index
// after
const updated = await refresh(credential);
try {
  storage.updateCredential(provider, id, updated);
} catch (e) {
  // credential was removed/disabled mid-refresh — re-acquire instead
  const fresh = await authenticate(provider);
}
Defensive patterns

Strategy: try-catch

Validate before calling

const exists = (await storage.listCredentials(provider)).some(c => c.id === id);
if (!exists) throw new Error(`Credential ${id} no longer exists; re-authenticate`);

Try / catch

try {
  await storage.updateCredential(provider, id, refreshed);
} catch (e) {
  if (e instanceof AIError.ValidationError && /No credential with id=/.test(e.message)) {
    const fresh = await authenticate(provider); // credential vanished mid-flight
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the credential update/refresh API while another caller concurrently disables or removes the same credential id; refreshing a credential whose id no longer exists in the provider's credential array; a race between an auth-broker refresh and a concurrent credential list mutation.

Common situations: Multiple processes (e.g. the auth-broker server and a CLI session) sharing auth storage where one deletes or disables a credential while another is mid-refresh; stale UI or cached id passed after credential removal; credential rotation that drops rows during a long-running token refresh.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/98e88b124e44b5da. Report an issue: GitHub.