Assrt token: which token Assrt uses, where it lives on disk, in what order it resolves
People typing “assrt token” into a search bar are usually trying to figure out one of two things: which token Assrt actually authenticates with, or how to get the Playwright extension token working with Assrt’s extension mode. The answer is both. Assrt resolves two distinct tokens, neither of them issued by Assrt itself, and the source code defines a precise priority order for each. This page is a reference for both, with the file paths and line numbers so you can verify it yourself.
Direct answer, verified 2026-05-06
Assrt does not issue its own tokens. It uses two existing ones. (1) An Anthropic credential resolved by getCredential() in assrt-mcp/src/core/keychain.ts: first the ANTHROPIC_API_KEY env var, otherwise on macOS the OAuth access token from the Keychain entry named Claude Code-credentials. (2) An optional Playwright extension token resolved by resolveExtensionToken() in assrt-mcp/src/core/browser.ts: first an explicit parameter, then the PLAYWRIGHT_MCP_EXTENSION_TOKEN env var, then the saved file at ~/.assrt/extension-token. The full source for both is below.
“The exact macOS Keychain service Assrt reads when no env var is set. Constant lives at line 10 of keychain.ts.”
assrt-mcp/src/core/keychain.ts:10
Two tokens, not one
The first thing to fix is the framing. There is no single “Assrt token.” Most tools in this space ask you to register, generate a key, paste it into an env var, and call it a day. Assrt does not do that. The product never mints a credential of its own; it composes two that already exist in the user’s environment. The first authenticates the LLM that runs the test. The second authorizes Assrt to attach to your real Chrome instead of launching its own. They are independent. Most users only ever need the first.
What people expect vs. what Assrt actually does
Sign up. Generate a workspace API key from a dashboard. Paste it into a CI env var. The vendor stores it server-side, rotates it on their schedule, and revokes it if you cancel. Your tests stop running the moment the vendor relationship ends.
- One vendor-issued token
- Lives in their cloud
- Tied to their dashboard
- Stops working if vendor changes
Token 1: the Anthropic credential
This is the credential the LLM agent uses to call the Anthropic API. Without it, no model call goes out and no test runs. The resolution function is small enough to read in full:
// assrt-mcp/src/core/keychain.ts
const KEYCHAIN_SERVICE = "Claude Code-credentials";
export function getCredential(): AuthCredential {
// 1. Explicit env var, works on any platform
const envKey = process.env.ANTHROPIC_API_KEY;
if (envKey) {
console.error("[auth] Using ANTHROPIC_API_KEY env var");
return { token: envKey, type: "apiKey" };
}
if (process.platform !== "darwin") {
throw new Error(
"No credentials found. Set ANTHROPIC_API_KEY, " +
"or run on macOS with Claude Code installed."
);
}
// 2. macOS Keychain (zero-setup if Claude Code is logged in)
try {
const raw = execSync(
`security find-generic-password -s "${KEYCHAIN_SERVICE}" -w`,
{ encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] }
).trim();
const parsed: ClaudeCredentials = JSON.parse(raw);
const token = parsed?.claudeAiOauth?.accessToken;
if (token) {
console.error("[auth] Using Claude Code OAuth token from macOS Keychain");
return { token, type: "oauth" };
}
} catch { /* keychain entry not found */ }
throw new Error("No credentials found. ...");
}Two sources, checked in order. The env var wins because it is portable and explicit; the keychain wins on macOS dev machines because it requires zero setup if Claude Code is already installed. The branch at line 40 is the seam: on Linux or Windows with no env var, the function throws immediately and tells the user what to do, instead of silently hanging on a shell command that does not exist.
Anthropic credential resolution priority
- 1. process.env.ANTHROPIC_API_KEY at keychain.ts:34. If non-empty, returned as { type: 'apiKey' } and the function exits.
- 2. On non-macOS with no env var: throw 'No credentials found' immediately (line 40-44). No keychain shell-out is attempted.
- 3. On macOS: shell out to security find-generic-password -s 'Claude Code-credentials' -w (line 48), parse JSON, pull claudeAiOauth.accessToken (line 52), return as { type: 'oauth' }.
- 4. On macOS with no keychain entry and no env var: throw the same 'No credentials found' error with a two-line remediation. No third source.
Inspect the Keychain entry yourself
The Keychain service name Claude Code-credentials is created and maintained by Claude Code, not Assrt. If you have ever logged in to Claude Code on this Mac, the entry is there. You can read it with the same command Assrt uses:
security find-generic-password -s "Claude Code-credentials" -w | jq .The output is a JSON object with the shape declared at keychain.ts:12-19:
{
"claudeAiOauth": {
"accessToken": "sk-ant-oat...",
"refreshToken": "sk-ant-ort...",
"expiresAt": 1730000000000,
"scopes": ["user:inference", "..."]
}
}Assrt only reads claudeAiOauth.accessToken. It never refreshes, never writes, never replaces the entry. If the access token has expired, the next test run will fail with an Anthropic API auth error and the fix is to log back in with claude on the command line, not anything Assrt-specific.
Token 2: the Playwright extension token
The second token is optional. You only encounter it if you opt into extension mode, which tells Assrt to attach to your already-running Chrome instead of launching a fresh browser. That mode is useful for testing flows behind authentication without re-logging in every run. The token is what the Playwright MCP Chrome extension issues to authorize the bridge.
The relevant constants and resolution function:
// assrt-mcp/src/core/browser.ts
private static readonly EXTENSION_TOKEN_PATH = ".assrt/extension-token";
private async resolveExtensionToken(tokenParam?: string): Promise<string | null> {
const tokenPath = join(homedir(), McpBrowserManager.EXTENSION_TOKEN_PATH);
// 1. Explicit parameter (highest priority); also persisted for next time
if (tokenParam) {
mkdirSync(join(homedir(), ".assrt"), { recursive: true });
writeFileSync(tokenPath, tokenParam.trim());
console.error("[browser] extension token saved to ~/.assrt/extension-token");
return tokenParam.trim();
}
// 2. Environment variable
const envToken = process.env.PLAYWRIGHT_MCP_EXTENSION_TOKEN;
if (envToken) return envToken.trim();
// 3. Saved file
try {
const saved = readFileSync(tokenPath, "utf-8").trim();
if (saved) return saved;
} catch { /* file does not exist */ }
return null;
}Note the side-effect on the parameter branch: pass an explicit token once, and Assrt writes it to ~/.assrt/extension-token for you. From the next run on, the saved-file branch resolves it without you doing anything. This is the setup-once behavior the README promises.
Extension token resolution priority
The first-use flow: what happens before the token exists
On the very first call with extension: true, no token has been saved yet. resolveExtensionToken returns null and the launch path throws an ExtensionTokenRequired error. The error is not opaque; the constructor at browser.ts:13-24 ships a five-step setup string. When called via the MCP tool, the server catches that error and returns a structured response with error: "extension_token_required" plus a hint instructing the agent to ask the user for the token. The flow looks like this:
Extension token, first-use flow
User calls assrt_test with extension: true
The MCP tool forwards the call into McpBrowserManager.launchLocal at browser.ts:258.
resolveExtensionToken returns null
No parameter, no env var, no saved file. Function returns null on the third miss.
ExtensionTokenRequired thrown
browser.ts:302. Error message contains the five-step setup instructions.
User runs npx @playwright/mcp@latest --extension
Approves the dialog in Chrome, copies the PLAYWRIGHT_MCP_EXTENSION_TOKEN value.
User retries assrt_test with extensionToken set
Token is saved to ~/.assrt/extension-token at browser.ts:238 and the run proceeds.
The ExtensionTokenRequired message itself is hard-coded at browser.ts:13-24 and reads, verbatim:
Extension mode requires a one-time setup token.
To set it up:
1. Make sure Chrome is running with the Playwright MCP extension installed
(install from: https://chromewebstore.google.com/detail/playwright-mcp-bridge/gjloebkfhhlbhemfgnmpjafamelkidba)
2. Run this command in your terminal:
npx @playwright/mcp@latest --extension
3. Approve the connection in the Chrome dialog that appears
4. Copy the token shown (PLAYWRIGHT_MCP_EXTENSION_TOKEN=...)
5. Paste the token value here so I can save it for future useTwo points worth highlighting. First, the Chrome extension is the issuer; Assrt only consumes its output. Second, the “paste the token here” phrasing assumes you are talking to a coding agent (Claude Code, Cursor, etc.) that can call the MCP tool again with the new value; the agent passes the pasted string as the extensionToken parameter, and the auto-save side effect kicks in.
What “assrt token” does not mean
Worth being explicit about a few things this term does not refer to, because the topic gets muddled.
- Not a cryptocurrency. There is no Assrt token on any chain, no airdrop, no presale, no contract address. Assrt is an open-source test automation framework; the only thing it issues is npm packages and CLI output.
- Not a workspace API key issued by Assrt. Comparable test SaaS products (the ones that cost five figures a month) issue their own API keys. Assrt does not. There is no dashboard at assrt.ai that mints credentials. The web app at app.assrt.ai is a viewer for runs, not an issuer of keys.
- Not an Anthropic-issued OAuth token Assrt creates. The OAuth token in the keychain is created by Claude Code when you log in there. Assrt only reads it. If you have never used Claude Code on this machine, that path resolves to nothing and you fall back to ANTHROPIC_API_KEY.
- Not a Playwright session cookie. The extension token is an approval token for the Chrome bridge, not a session credential for any site under test. Cookies and localStorage for the sites you test live in
~/.assrt/browser-profile(the persistent profile) or in your real Chrome user-data-dir (extension mode), separately from the bridge token.
Rotating, revoking, and where the bytes actually live
Both tokens live on disk in standard locations and rotate via the issuing tool, not Assrt.
Anthropic credential
- Env var: set or unset in the shell, CI secret, or systemd unit.
- Keychain entry: created and rotated by Claude Code itself. Logging out via
/logoutin Claude Code, or using Keychain Access.app to delete theClaude Code-credentialsgeneric password, removes Assrt’s ability to resolve it. - Revocation: per the issuing surface (Anthropic console for API keys, Claude Code logout for OAuth). Assrt has no part in revocation because it never minted anything.
Playwright extension token
- File on disk:
~/.assrt/extension-token. Plain text. Permissions inherit your home dir. - Env var override:
PLAYWRIGHT_MCP_EXTENSION_TOKENtakes precedence over the file but is below an explicit parameter. - Rotation: rerun
npx @playwright/mcp@latest --extension, copy the new token, pass it once via the parameter, and the auto-save overwrites the old file. - Revocation: uninstall the Playwright MCP Chrome extension or remove its approval; the Chrome side stops accepting the bridge regardless of what is on disk.
How both tokens flow into a single test run
For a normal headless run, only the Anthropic credential matters. For an extension-mode run, both are read and consumed at different layers. The agent never touches the extension token, and the browser layer never sees the Anthropic credential. The two paths converge only at the run-start log line.
Per-call token resolution and consumption
One call site for each token, one consumption site. The credential is constructor-injected into TestAgent at agent.ts:342 and never written to disk by Assrt. The extension token is passed through to the Playwright MCP child process and never sent to Anthropic. Crossing the two would be a bug, and the type system makes it hard to do by accident: AuthCredential and the extension token are different types in different files.
Audit your token surface in 20 minutes
If you are evaluating Assrt for a regulated environment and need to walk through every place a credential is read or written, a short call is the fastest way to get there.
Frequently asked questions
Does Assrt have its own API token I need to register for?
No. There is no Assrt-issued token, no signup form, no dashboard key to copy. Assrt is open source and runs locally; the only credentials it touches are an Anthropic credential (your ANTHROPIC_API_KEY env var, or your Claude Code OAuth token already in the macOS Keychain) and an optional Playwright extension token if you opt into reusing your real Chrome session. The credential resolution lives in /Users/matthewdi/assrt-mcp/src/core/keychain.ts and /Users/matthewdi/assrt-mcp/src/core/browser.ts. Read them to confirm.
Where exactly does Assrt look for the Anthropic credential?
In keychain.ts, the function getCredential() runs three checks in order. First, process.env.ANTHROPIC_API_KEY (line 34); if that is set, it returns immediately as type 'apiKey'. Second, on macOS only, it shells out security find-generic-password -s 'Claude Code-credentials' -w (line 48), parses the JSON, and pulls claudeAiOauth.accessToken (line 52); if found, it returns type 'oauth'. Third, on any other platform with no env var, or on macOS with no keychain entry, it throws a No credentials found error pointing the user back to those two options. There is no third source. There is no remote auth endpoint.
What is the Claude Code-credentials keychain entry and how does it get there?
It is created by Claude Code itself when you log in via the claude CLI. Assrt does not write to it; it only reads it. The benefit is that anyone who already has Claude Code installed and signed in gets zero-setup auth for Assrt: no env var to set, no API key to paste. Run security find-generic-password -s 'Claude Code-credentials' -w in a terminal to inspect it yourself; you will see a JSON blob with claudeAiOauth.accessToken, refreshToken, expiresAt, and scopes. The shape of that JSON is the ClaudeCredentials interface declared at keychain.ts:12-19.
What is the Playwright extension token and when do I need it?
It is a one-time approval token issued by the Playwright MCP Chrome extension when you bridge it to a CLI for the first time. You only need it if you call Assrt with extension: true (the MCP tool argument) or pass --extension to the CLI. That mode tells Assrt to attach to the Chrome you are already running, with all your cookies and logins, instead of launching a fresh browser. The token gates the bridge so a random script cannot silently take over your browser. Once issued, it is saved to ~/.assrt/extension-token and reused on every future run.
What is the resolution order for the Playwright extension token?
Three sources, checked in order, defined by the resolveExtensionToken function at /Users/matthewdi/assrt-mcp/src/core/browser.ts:228-255. (1) Explicit parameter passed to the call (highest priority); if present, it is also written to disk for future use. (2) The PLAYWRIGHT_MCP_EXTENSION_TOKEN environment variable. (3) The saved file at ~/.assrt/extension-token. If none of the three resolve to a non-empty value, Assrt throws an ExtensionTokenRequired error with a five-step setup string telling you how to mint one.
How do I get the extension token the first time?
Follow the exact steps from the ExtensionTokenRequired error message at browser.ts:13-24. (1) Make sure Chrome is running with the Playwright MCP extension installed (Chrome Web Store id gjloebkfhhlbhemfgnmpjafamelkidba). (2) In a terminal, run npx @playwright/mcp@latest --extension. (3) Approve the connection in the Chrome dialog that pops up. (4) Copy the token shown next to PLAYWRIGHT_MCP_EXTENSION_TOKEN=. (5) Paste it back into your Assrt call (extensionToken parameter for the MCP tool, --extension-token flag for the CLI). After that one paste, the token is written to ~/.assrt/extension-token and you never have to touch it again unless Chrome rotates it.
If I delete ~/.assrt/extension-token, what happens?
The next call to Assrt with extension: true will fall through to env var, then fail with ExtensionTokenRequired if PLAYWRIGHT_MCP_EXTENSION_TOKEN is not set. The fix is to mint a new token via the same five steps above. Deleting the file is also the right move if you suspect the saved token is stale or you have rotated Chrome profiles. Assrt has no remote token cache; the file on disk and the env var are the only memory.
Does Assrt send the Anthropic credential anywhere besides the Anthropic API?
No. The credential is read in-process by getCredential() and passed straight into the Anthropic SDK client used by the test agent (constructed at agent.ts:342). It is never written to ~/.assrt, never logged to disk, never sent to assrt.ai, and never copied into telemetry. Telemetry events fire from telemetry.ts and carry only event names, durations, and outcomes. If you want to verify, grep the source for ANTHROPIC_API_KEY and accessToken; you will see them at the resolution and consumption sites only.
Does ANTHROPIC_API_KEY override the keychain entry?
Yes. keychain.ts:34-38 checks the env var first and returns immediately if set, before ever touching the keychain. This is intentional; it lets Linux CI runners and any non-macOS environment use the same code path with no special-casing. On macOS, set ANTHROPIC_API_KEY in a shell only when you want to use a different key than the one Claude Code already cached for you, otherwise leave it unset and let the keychain resolution handle it.
Why does the source check the env var first instead of the keychain?
Two reasons. First, env vars work everywhere, the keychain shell-out only works on macOS (line 40-44 explicitly throws on non-darwin if the env var is unset). Second, env vars are the explicit override. If a user sets ANTHROPIC_API_KEY in a CI job or a shell, they almost certainly want that key used regardless of what is sitting in the keychain. The order optimizes for portability and explicit intent.
Same product, different angle
Adjacent guides
AI agent browser isolation: four layers, not one toggle
Where the persistent profile, isolated mode, and extension mode actually diverge in the source.
Deterministic, reproducible agent testing infrastructure
The seven knobs in assrt-mcp source that turn an LLM into a test runner.
Run tests locally, self-hosted
Running Assrt with no cloud dependency and no externally-issued credentials.
Comments (••)
Leave a comment to see what others are saying.Public and anonymous. No signup.