Skip to content

feat(omp): add an omp extension that follows .credentials.json - #27

Merged
countzero merged 2 commits into
developfrom
feat/omp-integration
Sep 30, 2026
Merged

countzero merged 2 commits into
developfrom
feat/omp-integration

Conversation

@countzero

@countzero countzero commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

What

Adds integrations/omp/claude_credentials.ts, an optional extension for omp (oh-my-pi). It serves omp's built-in anthropic provider from Claude Code's .credentials.json, so sca switch and sca monitor switch omp as well, with no omp restart. It is used from the git checkout by listing its path under extensions: in ~/.omp/agent/config.yml; there is no release asset.

  • getApiKey reads the file again on every request and returns its token. When a read fails, for example inside sca's atomic replace, it keeps the last good token.
  • omp is kept at exactly one enabled Anthropic login, with no email or account id and refresh: "external" in place of the refresh token. The login is written at session start, on credential_disabled, and when the file's token differs from the stored one or a second login appears. It is written only when something changed.
  • refreshToken never calls the token endpoint. A token that is still valid is returned with its real expiry. An expired one triggers one claude -p run (--safe-mode, Haiku, 90 s timeout, shared by concurrent refreshes), and then the file is read again.
  • The extension's tests use node --test with an injected file reader, clock and claude runner, and fake tokens only. CI does not run them, so they are a local check; docs/testing.md and AGENTS.md say to run them when integrations/omp/ changes.
  • Docs: a README section Using oh-my-pi (omp); an omp line in sca help and in the Invoke-MonitorAction comment (no logic change); an omp (oh-my-pi) section in docs/claude-code-internals.md with the omp 18.4.4 facts the extension relies on and a recipe to re-check them; and how to run the tests in docs/testing.md and AGENTS.md.

Why

omp keeps its own Anthropic login and never reads Claude Code's, so until now sca had no effect on it. Three decisions shape the extension:

  • Read-only. A refresh rotates the refresh token and invalidates the old one. Claude Code, sca's Update-SlotTokens and opencode-claude-auth can already rotate the same token without coordinating; only Claude Code serializes its own processes. If omp refreshed on its own, that would make a fourth party, and one lost race leaves the slot holding a dead refresh token. claude -p refreshes under Claude Code's cross-process lock, so the fallback adds no new party.
  • One login, no identity. Two stored logins would turn on omp's own usage-ranked rotation, and sca monitor has to stay the only thing that rotates. omp's upsertOAuth adds a new login each time when there is no identity key, so the extension uses pool.set instead.
  • Error wording. omp permanently disables a login whose refresh error matches invalid_grant, revoked, refresh token expired, 401/403 and similar patterns, and only blocks it for 5 minutes on anything else. The extension's messages avoid those patterns, and a test enforces that.

Verification

  • node --test "integrations/omp/*.test.ts": 26 pass.
  • tests/Invoke-Tests.ps1: 1073 pass, coverage 98.8% against the 97% gate.
  • Live, against omp 18.4.4 on Windows: an omp run on Haiku answered, and its stored login matched the active slot. During a second run that was waiting in a 25-second command, sca switch changed accounts; the run finished, and omp's stored login then matched the new slot. The check compared tokens by hash only.

The live run caught one bug before this commit. omp's pool.set does not delete the logins it replaces; it keeps each one, disabled as "replaced by newer credential". The first version re-seeded at every session start and added one disabled entry per omp run. The extension now writes only when the stored login differs, and the internals doc states the corrected fact.

Shortcomings

  • It depends on omp internals. ctx.modelRegistry.authStorage.credentials, the shape of entries() and the disable patterns are not a public omp API. A new omp release can break them silently. When the credential pool is missing, the extension warns and stores nothing, but a changed disable pattern would only show up as a disabled login.
  • Usage polling and model discovery send the stored login's token, not getApiKey's. Between a switch and the next request, they still use the old account. The extension updates the stored login on the first getApiKey after a switch; it has no way to see a switch before that.
  • The fallback costs money and time. It spends ~$0.004 and up to 90 s. It can't start an npm-installed claude.cmd on Windows, only the native executable.
  • It isn't compatible with an omp auth broker (OMP_AUTH_BROKER_URL, auth.broker.*), which replaces the custom refresh with omp's own. The README says so, but the extension doesn't detect it.
  • A /login anthropic inside omp adds a second login. The extension replaces it on the next request, rather than preventing it.

Feedback wanted

  • The read-only design and the claude -p fallback: is one run of Claude Code the right place to put expiry, or should the extension refuse and leave expiry to sca monitor?
  • The trigger conditions for re-seeding in getApiKey: they fire on a token mismatch or on more than one enabled login.
  • Whether the omp section in docs/claude-code-internals.md meets that file's admission rule. The re-check recipe greps the omp source rather than running a probe.

Not done

  • The expiry fallback hasn't run live. It needs a token that has actually expired; it's covered by the fake-based tests only.
  • Only Windows has been tested, live or otherwise. CI does not run the extension tests, so nothing has run them on Linux or macOS.
  • No CHANGELOG.md entry; that's written as a release step.

omp keeps its own Anthropic login, so sca switch and sca monitor never reached it. The extension serves omp's anthropic provider from Claude Code's .credentials.json, re-read on every request, so omp follows a switch without a restart.

It is read-only: it never calls the token endpoint and stores 'external' in place of the refresh token, because a fourth party rotating the same refresh token would invalidate the copy in the slot. An expired file is renewed by one claude -p run, which refreshes under Claude Code's cross-process lock. omp is kept at exactly one enabled row so its own usage-ranked rotation never competes with sca monitor, and the row is written only when the token changed, since omp keeps every replaced row as a disabled one.

Verified live against omp 18.4.4: a running omp session followed an sca switch mid-request.
The extension is optional and used from a checkout, so its tests stay a local check rather than spending a three-OS matrix on every pull request. docs/testing.md now says CI does not run them, so nobody reads a green run as covering the extension.
@countzero
countzero merged commit f6c8a61 into develop Sep 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant