Connect reference
What Connect writes and runs on a machine, for security review and fleet
operations. Connect's runtime runs per user, without privilege. Native packages and
system-wide managed-enrollment bootstrap files are installed by IT with administrator
privileges; managed --write also needs privilege for fleet policy. Connect installs no CA
certificates, machine-wide proxy, DNS changes, or kernel extension. Nothing sits in the request
path: tools call the gateway directly, so Connect never sees their method, path, body, or
response.
Direct fallback
The tray checks the gateway's unauthenticated /livez endpoint every 60 seconds. After two
failed checks at least 30 seconds apart, it can switch Claude Code and Codex to their native
subscriptions if their provider answers. Network errors, timeouts and HTTP 5xx count as down. Other HTTP answers, including
401 and 403, never trigger a switch.
Claude Code switches on the next prompt and keeps the conversation history. Re-send the failed message. Codex uses the direct route for new conversations in the running app. Open Codex conversations switch after a restart.
Automatic switching follows the cached autoFallback setting from /connect/policy.
A gateway that cannot read its settings leaves the last known value in place. Missing means on.
A tool switches only if:
- It has a native subscription login. The manual command also accepts your own pre-Anyray provider key when there is no subscription.
- Codex already uses the subscription lane.
- No IT-managed route or setting could send an Anyray key to a provider.
- For Claude Code, you have no
apiKeyHelperof your own. Connect's helper is set aside while direct and put back after.
While automatic fallback is active, the same /livez checks measure ten uninterrupted
healthy minutes. A failed check resets the timer. The tray then checks your current enrolled
key once before restoring the route. A rejected key starts a fresh healthy window. Regular
authenticated checks keep their five-minute cadence, with 30-second retries when needed. Manual
switches stay direct until you revert them.
anyray-connect fallback
anyray-connect fallback status
anyray-connect fallback --revert
The manual switch needs no gateway probe and works when automatic fallback is off. Revert requires one authenticated gateway check, with no ten-minute wait. Older fallback markers still use the full restore path and require a restart.
Both switches preserve hooks, MCP registrations and other settings. Refresh and apply leave an active switch's routing alone while gateway key renewal continues. Revert restores Claude Code's saved env values, including absent keys, and Codex's provider table with the current gateway key. Direct calls bypass Anyray spend governance.
doctor and status --json report directFallback: off, direct with mode and tools,
unreadable, or blocked with reason org_policy_off, no_subscription, personal_key,
managed_route, gateway_credential, or unknown. An unreadable marker reports
“Direct fallback state is unreadable; run anyray-connect fallback --revert”. Revert quarantines
the marker and re-applies the gateway settings. Doctor also flags a project .claude/settings.json
base URL override.
What lands on the machine
CLI state lives in ~/.anyray/ (override the root with
$ANYRAY_HOME), created mode 0700; credential-bearing files are written 0600.
| Path | What it is |
|---|---|
bin/anyray-connect | The managed standalone binary. Every hook, MCP registration, and service points at this one stable launcher. |
versions/<version>/ | Previously installed versions, kept so an update can roll back. |
connect.json | This machine's profile: gateway address, your name and team, which tools are configured. Mode 0600. |
test-kit.env | Gateway URL, client-test key and expiry written by test-key. Atomically replaced, mode 0600. The key is never printed. |
direct-fallback.json | Direct-provider fallback marker. Light switches save their mode, tools, recovery timer, and previous env values or provider table for exact restoration. Mode 0600, never printed. Its presence makes already-loaded Connect hooks pass through. |
ca/gateway-roots.pem | Only for an npm Claude Code that cannot read the OS trust store: the company root or roots the OS store used to verify your gateway, nothing else. Public certificates, mode 0644. Removed once Claude Code no longer needs it (Gateway certificate). |
dev-key.pem | The device private key that proves this machine's identity when renewing your gateway key. Mode 0600, never logged or printed. |
logs/connect-refresh.log | Captured output from scheduled key renewal, where installed. Error names only, no request data. macOS only; Linux logs to journald. |
desktop-update.json | The desktop app's last update attempt: its outcome, the step that failed, the version it tried, when, and how many attempts in a row failed. Mode 0600, no paths or error text. See Desktop app updates. |
vscode-extension/, vscode-receipts/ | The bundled Anyray VS Code extension and a receipt per applied install. |
openclaw-plugin/ | The OpenClaw plugin. The Anyray repository is private, so Connect and your gateway are its distribution channels. |
client-assets/ | Newer VS Code extension and OpenClaw plugin builds your gateway serves, stored by sha256 and rechecked on every read. Connect installs one only with a valid Anyray release signature and only if it is newer than the bundled copy. |
hook-tee/ | Recovery copies of replaced tool output. Owner-only, never uploaded, kept 30 days after the last reference, 90 days at the outside, and 128 MiB in total. Under a Codex workspace-write sandbox the copies go to anyray-hook-tee-<uid> in the temp directory instead, with the same rules and its own 128 MiB. ANYRAY_HOOK_TEE=off disables both. |
hook-cache.json | Optimization verdicts for outputs already seen, so a repeat skips a second round trip. 24 hours, 200 entries. ANYRAY_HOOK_CACHE=off disables it. |
hook-stash-lane.json | Whether the gateway offers the hookStash digest lane: a hashed gateway origin, yes or no, and the check time (every 6 hours). Mode 0600. |
source-trim/ | Daily logs measuring the Codex source trim: one line per command the hook rewrote and per wrapped run (how its full copy was kept, and the output's size before and after). Counts and sizes only, never a command, path or output. Kept 7 days. A run inside Codex's sandbox writes its line to anyray-source-trim-<uid> in the temp directory, and the next Codex hook call moves it here. Uninstall removes both. |
hook-tee-ledger.json | Which tee files the hook named in each session, plus per-command-shape counts of trims, read-backs and read-back slice sizes that feed the ranged-trim check. When a session reads a digest's tee back at all (a whole or partial Read, a sed range, an anyray_retrieve range), the hook stops trimming that command shape (such as git show) for the rest of the session; a ranged read-back only updates the counts, and so does running the same command again within 10 minutes of a ranged trim, which counts as a full read-back. It also remembers whether the gateway offers ranged trims (a hashed gateway origin, a yes or no, and when it was checked). Stores hashed session ids, tee ids and hashed command shapes, never paths, commands or output. Mode 0600. At most 64 sessions (idle for 24 hours: ignored, removed at the next write) and 256 command shapes (dropped after 30 days idle). ANYRAY_HOOK_TEE=off disables it. doctor --json reports its counts as hookTeeLedger (teeRereads, suppressedShapes, rangedEmits, rangedRereads, meanRangedSlice). |
hook-digest-routers.json | Which digest type (test-run, grep, log-dedup) and version produced each tee a session named, or each test-log page the hooks.logRead digest shortened, and per type and version, digests sent and read back. hooks.digestEconomics prices digests with these counts; they are kept whether it is on or off. Stores hashed session ids and test-log keys (a hash of the session id and file path), tee and page ids, type names, versions, counts and timestamps, never paths, commands or output. Mode 0600. At most 256 tees and pages (ignored after 24 hours) and 16 type versions (dropped after 30 days idle). ANYRAY_HOOK_TEE=off disables it. doctor --json reports the counts as hookTeeLedger.digestRouters. |
hook-fleet-readback.json | What your gateway last reported for each command shape the hook trimmed, across every seat in your organization: trims, how many were read back, and the organization-wide rate. Stores hashed command shapes and gateway origins, counts and a timestamp, never paths, commands or output. Mode 0600. At most 256 shapes, ignored after 30 days. With hooks.fleetReadBack on (default off), the hook passes a shape's output through undigested when the fleet keeps reading its digest back, priced per output size. Off, it only counts what it would have done. ANYRAY_HOOK_TEE=off disables it. doctor --json reports its count as hookTeeLedger.fleetShapes. |
hook-arrival-rates.json | The prompt-cache read and write rates and the output rate your gateway serves for each model this machine's Claude Code sessions run, so the ranged-trim check can price a trim the way the provider prices that model, plus the measured turns left (horizon) when the gateway serves one. Asked from GET /connect/policy?model=<id>&clientTool=<id> at most every 6 hours per gateway, model, client tool and enrollment (enrolling again, say into another tenant on the same gateway, asks again), every 30 minutes while the gateway serves rates without a horizon, and 10 minutes after a failed ask, only where the gateway offers ranged trims. Stores a sha256 of the gateway, model, client tool and the enrollment's device public key (a hash, not a secret), rates, the horizon and timestamps, never the key. Mode 0600. At most 16 entries. ANYRAY_HOOK_TEE=off disables it. |
hook-arrival-shadow.json | Both verdicts on each ranged-trim decision: the shipped check, and the same check priced on the rates above (hooks.rangedArrival). Counts per stage (precheck, final) of where the two agree and differ, why the second could not be priced, and both verdicts on each trim that went out. A pre-check both decline is not counted. Stores hashed session ids, tee ids, true or false, reason names and numbers, never paths, commands or output. Mode 0600. At most 256 trims, dropped after 30 days. ANYRAY_HOOK_TEE=off disables it. doctor --json reports the counts as hookTeeLedger.arrivalShadow. |
tee-bet-salt | A random secret for this machine, mode 0600. When a trim names a tee file, and when the model reads one back, the hook tells your gateway with an HMAC of the tee id under this secret, never the id itself. The gateway can match the read to the trim but cannot test a guessed output against it. Without this file the hook reports neither. |
hook-log-reads.json | With hooks.logRead on: which test logs each session's test commands wrote, so only those Reads are shortened. Stores hashes of the session id and file path, the file's size and modification time, the runner name, the line numbers a digest covered, whether the session read any back, and counts. It never stores paths, commands or output. Mode 0600. At most 256 records (after 24 hours: ignored, removed at the next write). doctor --json reports its counts as hookLogRead (recorded, emits of failing runs, greenEmits of passing runs, fullRereads, rangedRereads, and declined by reason). |
mcp-served/<pid>.json | What each running Anyray MCP server serves for batch reads (ranged, files or off), its process ID and when it decided. Written once per server, pruned when the server is gone. Contains no content. Mode 0600. |
hook-policy-state.json | Experimental source-trim counters and bounded session metadata. Stores hashes, handles and tool-call numbers, never file paths or content. Mode 0600. |
hook-policy-observation-gap | A bookkeeping failure is awaiting recovery. The next successful metadata write resets read history and clears the signal; the cumulative gap counter survives. Contains no content. |
The desktop inventory has four categories: the installed app and its bundled engine, the OS login registration, the local profile, and the resident engine process. These are categories, not a four-file limit. Tool integrations, logs, update staging and supporting state remain part of the security inventory.
On macOS 13 or later, login uses Apple's Service Management registration. Windows uses the per-user Run entry; Linux uses the desktop autostart entry. On Windows, if the app's installed location moves, the app rewrites the Run entry to point at it on its next launch; an entry the user disabled in Task Manager stays disabled. Desktop maintenance runs in the bundled engine on the app's timer, without a separate refresh scheduler. Quitting the app stops renewal until it runs again; it does not remove the login registration. Disabling that registration in OS settings stays effective after reopening the app. When reconciliation confirms the app was removed, Connect clears desktop ownership but does not reinstall the CLI refresh scheduler. The saved scheduler intent remains, so doctor reports it as enabled but not installed.
Upgrades retire the old macOS tray job after ownership and native login state are recorded.
A short-lived bundled engine worker survives unloading the old job by using a separate
process group. Interrupted removal leaves a .plist.retiring marker for retry. IT handles
system-wide enrollment bootstrap cleanup
separately from this per-user migration.
The running tray reads the login setting every 15 seconds. Changes are saved and reported when detected; unchanged observations are checkpointed every five minutes. Machines shows the last reported native setting and its original timestamp per user. An observation becomes stale after 15 minutes, even if other seat activity continues. Disabled, unknown and stale login settings do not lower routing compliance. These reports use the existing client key without minting a replacement or running maintenance. The gateway's existing renew-on-use rules still apply when it authenticates that key. Older gateways ignore this additive observation; upgrade the gateway to see it.
Maintenance runs every 30 minutes and immediately on ownership handover. If scheduler teardown fails, retries back off from one to 30 minutes. A successful maintenance pass can authorize teardown-only retries for 30 minutes; after that, maintenance runs again. Each pass re-applies a managed tool that stopped pointing at the gateway or still holds an old key, at most twice a day per tool.
Seams inside your tools
Beyond each tool's base URL and key, these are the named seams Connect writes. Each carries an
ownership marker, so a re-run is idempotent and --revert removes exactly what Connect added,
leaving anything it doesn't own untouched.
| Seam | Where | What it does |
|---|---|---|
PostToolUse hook | ~/.claude/settings.json | Always installed. hooks.digest (default on) shortens recognized command output; off passes it through. Test-run output under 5,000 characters reaches the model as printed. A larger test run whose summary counts a failure the digest would not show (for node --test, # fail N or ℹ fail N) is not digested either. When a single test run exits 0 and Claude Code saved its long output to a file, hooks.digest adds one note that the run passed and the saved file need not be read; it says nothing unless the file holds no failure line. A digest's footer names its local hook-tee copy, or, when the gateway's hookStash setting is on and anyray_retrieve is registered, a ctx_ handle for an encrypted copy the gateway keeps for 48 hours. hooks.readTrim (default off) trims large Claude Code Read results reversibly when anyray_retrieve is registered and approved, and the footer names the ctx_ handle that returns the full file. With hooks.readTrimRequiresRetrieve: on, it waits until the session has made a successful anyray_retrieve call. hooks.digestEconomics (default off, needs hooks.digest or hooks.logRead) prices each digest before sending it: the tokens it saves for the rest of the session against how often this machine reads that kind of digest back (counted per digest type in hook-digest-routers.json) times what a read-back costs. A type read back too often for its saving, such as a short test-run digest, passes the output through unchanged; grep digests, which are rarely read back, still go out. Without a transcript to read usage from, it prices the digest as if 9 requests remain and a read-back costs 37,000 input tokens. It covers command-output digests and the hooks.logRead Read digest, which it prices with the test-run counts, so test-run read-backs on either path count against both. It runs only on a machine that keeps tee files, so not with ANYRAY_HOOK_TEE=off. Each time it passes an output through, the hook tells your gateway the tool name, the digest type and version, the digest's counts and the output's size, never the output, and the call's trace shows a command digest marked standAside with nothing removed. hooks.logRead (default on) shortens a Read of a failing test log, the whole file or one page of it (offset and limit), to its summary and failure blocks, each kept whole however long, but only when a test command in the same session wrote that file (go test ./... > F, >> F, &> F, | tee F), nothing later in that command could write it again, and the file has not changed since. Kept lines keep their line numbers. Each cut is one marker that names its lines and the Read offset and limit that return them, so the file itself is the way back. The first marker's footer says every failure in the log is shown in full and the omitted lines are passing or skipped tests and runner progress; the built-in anyray skill tells agents the same, so they don't read the log back. A page keeps lines at its edges that may belong to a failure block above or below it. Claude Code cuts a plain Read of a long log short (a go test -v log of 1,946 lines returned lines 1-885), and that Read is shortened as the log's first page. A passing run's log, or a page of it, is shortened to the runner's verdicts (go's ok and ? … [no test files] lines and PASS, or another runner's final summary), its skipped tests and its first and last lines, at their own line numbers behind the same markers, but only when the whole log shows no failure and the runner's own verdict says the tests passed (a page is checked against the whole file, read up to 4 MiB); these count as greenEmits in doctor --json hookLogRead. A log whose runner never said it passed, a page of a failing run with no failure block or failing verdict on it, a page under 1,000 characters, an offset/limit Read of fewer than 150 lines (a window the model aimed at; hooks.readTrim leaves it whole too), and a failing run whose every failure would not keep its header pass through untouched. Once the session Reads lines a marker named, or the whole file after a digest, every later Read of that file passes through until a test command writes it again. The gateway shows this lane as its own strategy, log_read, and when it leaves a Read of a log it owns whole it tells the gateway the reason (a fixed list such as reread, ranged, changed or no_failures) and the output's size, never a path or content. After each policy pull Connect also reports whether the lane runs on this seat (active, off by policy, or not available without Claude Code hooks), in a report of its own and only to a gateway whose policy lists client-strategy-log-read-v1 under capabilities; an older gateway is sent nothing. While the gateway's regret guard holds the lane back for the org, the policy it serves reads hooks.logRead: off with a hooks.logReadGuard marker; Connect keeps the lane's posture and reads a Read of a log it owns whole, reporting the reason guarded, until the marker's until passes or the next pull clears it. An older Connect honors the off. While the Measurement card measures the lane, the policy carries hooks.logReadHoldout (a salt and a share). Connect holds a session out when the draw from the salt and Claude Code's session_id falls under the share: that session's Reads of a log the lane owns reach the model whole, exactly as with the lane off, and Connect reports the reason holdout with the size of the digest it would have served, never a path or content. |
PostToolUseFailure hook | ~/.claude/settings.json | Always installed. Failed command output passes through unchanged. Claude Code cuts a long failing output itself: it keeps the start and the end and drops the middle, so failures in the middle can be lost. When a failing test run's output is long enough that Claude Code has most likely cut it, hooks.digest (default on) adds one hint. For a go test it names the run's own command without -v, piped through a grep for the failure lines under set -o pipefail (so the run keeps its exit status), to list the failing tests, and then go test -run '^TestName$' for each one; for other runners, a failures-only rerun. It never suggests saving the whole output to a file and reading it. The hook counts each hint it adds in hook-hints.json, counts only. With hooks.logRead on, it also records which log a failing test command wrote. With hookPolicies.greenCollapse on, it also counts a failed retrieve of a collapsed result. |
Lifecycle SessionStart, PreCompact, SessionEnd hooks | ~/.claude/settings.json | hooks.lifecycle (default off) records lifecycle receipts. A machine running a hook experiment that needs receipts turns this on locally. |
Key-refresh SessionStart hook | ~/.claude/settings.json | No policy switch. Subscription mode only: re-mints a short-lived key at session start, so it can't lapse mid-session. Runs async, never delaying startup. |
anyray MCP server | ~/.claude.json under mcpServers | The retrieval server, described below. Re-invokes the same binary over stdio. When the gateway's claudeCodeRemoteMcp setting offers its own /mcp, the next policy pull replaces it with anyray-retrieval, an http entry at that URL carrying the seat's key, and adds its tool approvals first. New Claude Code sessions use it; a running session keeps the server it started with. It switches back only after two policy pulls in a row leave the offer out. revert fails if it cannot remove the entry, which holds the key. doctor --json reports the transport as mcp.transport (stdio or http). |
| MCP tool approvals | ~/.claude/settings.json under permissions.allow, ~/.codex/config.toml as [mcp_servers.anyray.tools.*], opencode.json under permission | Pre-approves the managed tool set for each host. Symbol context requires separate Claude Code approval. Connect adds approvals only beside a registration it owns: where another MCP server holds the name anyray, the approvals are skipped, and withdrawn if that name changes hands later. A setting you wrote yourself for one of these tools is kept, and only that tool's approval is left out. |
| Cursor hooks and MCP | ~/.cursor/hooks.json, ~/.cursor/mcp.json | Cursor's native Team mode: the local Shell, Read, and MCP optimization seams, plus the same anyray retrieval server. |
| Managed shell block | ~/.zshrc, ~/.bashrc, or ~/.profile | Base-URL environment variables, fenced by # >>> anyray connect >>> and # <<< anyray connect <<< so the rest of your profile is never rewritten. |
| VS Code extension | VS Code's own extension store | Installed through the VS Code CLI from the bundled VSIX, or from a newer one your gateway serves once its sha256 checks out; its credential goes to VS Code SecretStorage, not to a file. |
Turning lifecycle or prompt off removes its Claude Code hook entry on the next refresh. hooks.shellRewrite is retired: Connect ignores it and removes its old PreToolUse Bash entry. PostToolUse stays installed; digest: off passes command output through, digestEconomics: off sends every digest that is shorter than the output, logRead: off leaves every Read whole and records nothing, and fleetReadBack: off digests a shape whatever the fleet's read-backs say.
When the gateway serves hooks.sessionGate, a key drawn into the session gate's connect_hook holdout passes every output through untrimmed, with no hints, for the whole Claude Code session. Codex and Cursor are never drawn. A lane the gate turned off does the same outside the treated arm.
When your org has an input guardrail that can refuse or rewrite a request, the gateway runs your
input guardrails on each output the hook sends before trimming or stashing it, as on the seat's own
request, under the model the seat last used. A redaction applies to what the gateway trims and keeps.
A refused output comes back untrimmed, and nothing is stashed or booked. With such a guardrail and a
routing config that picks its target by request (a conditional route), every output comes back
untrimmed and nothing is stashed, because the check runs on the hook's own request and could land
on a different target than the seat's requests. A managed source hook whose output a redaction
changed is not trimmed, since its check that anyray_retrieve returns the raw output would fail. A partner guardrail (a billed service or a
webhook) runs once more for each output the hook sends.
Where your gateway offers ranged trims, every output the ranged-trim check looks at is priced twice: with the shipped check, and with the same check on the rates your gateway serves for the session's model (Opus 5.5 reads its cache at 0.05x, and a session that writes the one-hour tier pays 2x, where the shipped check assumes 0.1x and 1.25x for every model). hooks.rangedArrival (default off) decides which one sends a trim. Off, the shipped check decides and both verdicts are counted in hook-arrival-shadow.json. The second check prices the turns a trim is carried over on your tenant's measured horizon when the gateway serves one, and on half the requests so far when it does not. Each trim that goes out also reports both verdicts and their predicted nets to the gateway on its bet, so an admin can compare the two checks per command (hookArrivalShadow on GET /admin/connect-health). A trim only the second check would send is reported too, as a count with the command's name, a predicted net and a size bucket, never the command line or the output. On, the second check decides; when the gateway serves no rates for the model (an older gateway, or a model it prices only by fallback), the shipped check still does. Ranged trims run only for a tenant that turned on rangedHookTrim.
A tool call made inside a Claude Code subagent gets the digest and its local hook-tee footer. It gets no ranged trim, and no ctx_ handle except from the Read trim below: a subagent can be limited to a few tools, so it may have no way to fetch what was cut. On a seat that was offered the gateway's /mcp, the digest stands aside too.
With hooks.readTrim on, a Read in a built-in general-purpose, Explore, or Plan subagent is trimmed as in the main session. Those subagents inherit the session's MCP tools, so they can call anyray_retrieve. Connect checks the type against the session's agent list, so a custom agent that reuses one of those names gets no Read trim.
The retrieval MCP server
The retrieval server is what makes an optimizer trim reversible: where a strategy replaces
bulk with an opaque ctx_… handle, these are the read paths that fetch the detail back.
Every client gets anyray_retrieve and anyray_recall, plus anyray_history where that
archive has content. Clients whose native search and read output cannot use Anyray's
replacement hook also get anyray_search and anyray_read. Claude Code does not advertise
those two context tools by default: its native Read already passes through the PostToolUse hook, so a
fresh session does not duplicate its first-turn exploration through MCP.
The batch search prototype can expose search alone on Claude Code.
Claude Code subagents (the Agent tool) inherit the session's MCP servers, so their requests
carry anyray_retrieve and anyray_recall too.
After initialization, the stdio server sends a content-free liveness heartbeat every 5 minutes until the transport closes. The gateway expires that evidence after 6 hours without verified activity, so a stopped server automatically disables fresh handle-producing strategies.
| Tool | What it does |
|---|---|
anyray_retrieve | Fetch the full original behind a known ctx_… handle. |
anyray_recall | Search the durable stash semantically when the handle has scrolled out of view, returning ranked handles. |
anyray_search | Search the local workspace and return file:line candidates, ranked and trimmed against the query. |
anyray_read | Read a workspace file, returned as a relevant slice the model can top back up rather than a lossy cut. |
anyray_history | Search your own past context. Advertised only where that archive has content. |
anyray_share | Publish an HTML, Markdown, SVG or React file to the console and return its link. See formats and limits. Advertised only on Claude Code and Claude Desktop while your admin has Artifacts on. |
anyray_list_shared | List the pages shared with you: title, owner, date and console link. Advertised on the same clients once your gateway can read shared pages. On an MDM-managed Claude Desktop, the gateway's managed anyray server serves it and anyray_open_shared instead (details). |
anyray_open_shared | Open one shared page by id and return its text, up to 64 KiB. Advertised with anyray_list_shared. |
Every result is capped before it leaves the MCP server. If a read reaches the limit, it stops
at a line boundary and names the next startLine; a search drops the lowest-ranked tail and
reports how many matches were omitted. This prevents an MCP host from rejecting the entire
result before the hook sees it.
When doctor reports the server as not registered, it names which of these it found, because
the fix differs: the tool has never been launched, no entry exists, an older Connect wrote the
entry, an entry under our name belongs to something else, the config could not be read, or a
second Connect install wrote it against a different executable. That last one means two installs
share this profile and each keeps reclaiming the other's registration. Upgrade both. Current
releases pick one owner from any copy: the per-user install that updates itself, unless the binary
your Claude Code managed settings run (managed --bin) is newer. Then that binary owns, and
doctor stops listing it as a conflict. Its hooks run in the per-user install while that install
is newer.
Batch search
Prototype, off by defaultanyray_search can run independent workspace queries in one call. Session savings are not measured.
This switch applies to Connect's local stdio MCP server. The HTTP retrieval transport does not offer workspace search.
Merge this field into the profile, keeping other settings:
{
"clientToolPolicies": {
"searchBatch": true
}
}
The MCP server reads the switch once per process. Claude Code then gets anyray_search;
anyray_read stays hidden. Other clients keep single-query search and gain the batch form.
Set searchBatch to false and restart to turn it off.
Approve mcp__anyray__anyray_search in Claude Code before trying a batch. Connect does not
pre-approve this prototype. For unattended trials, add that rule to permissions.allow in
~/.claude/settings.json, keeping the existing rules. Approval diagnostics cover the default
tool set and do not confirm this permission.
Check the running server's tools/list for anyray_search.inputSchema.properties.queries,
then call the tool with two identifiers from your workspace:
{
"queries": ["setupClient", "loadConfig"],
"globs": ["src/**"],
"maxResults": 20,
"resultShape": "full"
}
The result starts with SEARCH_BATCH_V1 2 and has numbered, quoted query labels in request
order. Check the matching source lines for both queries. A status report alone does not
prove the running client has the batch form.
| Detail | Value |
|---|---|
| Input | Either query or queries, never both. queries takes 2 to 8 distinct, nonempty strings after trimming, at most 256 characters each. |
| Shared options | globs, maxResults, and resultShape apply to every query. The batch default is 20 matches per query; the cap is 400. At most two searches run concurrently. |
| Output | Up to 40,000 characters, including labels. Each group gets a share of the limit; omitted output is labeled. Narrow the query or use a single search to recover it. Matching text keeps the existing search width limit. |
| Outcomes | No match, timeout, and widened term matches keep their own labels. A failed query has an error section; the other queries continue. Only an all-failed batch sets the tool result's isError. |
| Source hook | The hook preserves the grouped response without another output rewrite. Single-query handling stays the same. Sensitive-file exclusions still apply, including .env*, even with explicit globs. |
| Profile state | doctor --json and status --json report clientTools.searchBatch as off, enabled, or invalid_policy. Older reports omit it. This reports the profile switch; verify the running server after restarting. |
The anyray_search heartbeat counts include single, batched and refused calls, so they show
neither batch adoption nor savings.
Search with source context
Prototype, off by defaultanyray_search_context takes TS/JS declaration names and returns each one's complete definition in one
call. It is available to Claude Code through the local stdio MCP server on platforms with a
confined reader.
Open Console → Optimizer → Symbol context and turn it on. Use a targeting rule to enable it for selected users. Both client strategies are off by default (settings).
Restart Claude Code. The server reads the managed policy once per process. Approve
mcp__anyray__anyray_search_context before use; Connect does not pre-approve it. For unattended
trials, add this rule to permissions.allow in ~/.claude/settings.json, keeping existing rules.
Connect's approval diagnostics do not confirm this permission.
Turning the card off applies to new sessions. An explicit local
clientToolPolicies.searchContext: false also keeps the tool off. The HTTP retrieval transport and other
clients do not offer this prototype. An unavailable confined reader hides and refuses the tool.
While the tool is served, Claude Code's batch-read hints also
name it. clientToolPolicies.searchContextHints: false keeps the tool and drops that mention.
Check the running server's tools/list for anyray_search_context, then call it with names
from your workspace:
{
"queries": ["setupClient", "Client.connect", "#retry"],
"globs": ["src/**"]
}
Check each query's status line and the numbered source under each ## section header.
doctor --json and status --json show clientTools.searchContext; older reports omit it.
The running server reports its resolved availability under Users → Client strategies.
| Detail | Value |
|---|---|
| Queries | 1 to 16 names: compose, Client.connect, Client#retry, #retry, constructor Client. One string of names separated by commas, semicolons or newlines also works; repeats are dropped. Leading keywords (export async function) and anything from the first (, <, : or = are ignored. At most 16 shared globs. |
| Search | Whole-word matches in .ts, .tsx, .mts, .cts, .js, .jsx, .mjs and .cjs files only. Globs are path globs; ! excludes. .gitignore, hidden files and sensitive-file exclusions apply. Source files rank before declaration files, then tests, examples, docs and benchmarks. |
| Context | symbol is the default. It returns the innermost declaration that defines each name: a method with its class's opening lines, not the class. A class, interface or enum over 4 KiB comes back as an outline of its members. Another declaration over 10 KiB comes back as its first lines, labeled partial. |
| No definition | A name with matches but no definition gets at most 3 labeled windows of 5 lines from source files. A call site is never expanded into the declaration around it. |
| Windows | Set context: "window" for search windows instead: maxResults (default 5, at most 20) hits per query, before (default 3, at most 40) and after (default 8, at most 80) lines around each. |
| Bounds | Symbol results stay within 24,000 characters. Each query's first definition is kept first; anything left out is named by path and line. Window results keep the 32-range and 40,000-character bounds. |
| Outcomes | Query order is preserved. Each status is definition, no_definition_found, no_match, invalid_query or search_failed. |
Definitions exclude their dependencies, imports and references elsewhere. Read further when the returned source is insufficient. Sensitive-file exclusions and confined reads still apply. A candidate file the reader refuses or cannot read is skipped and counted on its own line; the call still answers. The source hook preserves the result. Native Grep with context and batch read remain available.
The profile state alone does not prove that an open session serves the tool. Heartbeat attempts
for anyray_search_context include refused calls, so they show neither retrieval nor savings. The
prototype adds no booked savings credit.
Local opt-in on older gateways
Gateways without managed strategy policy keep the local profile switch. Merge this field into the profile, keeping other settings, then restart Claude Code:
{ "clientToolPolicies": { "searchContext": true } }
Codex source trims
Connect registers a PreToolUse hook in ~/.codex/hooks.json matching Codex's Bash tool. When a
command is one Anyray recognizes as bulky-output (test runs, searches, git log, container logs),
the hook rewrites it to run through Connect, which trims the output before Codex ever sees it. The
trimmed bytes are what enter the transcript, so nothing already cached has to be rewritten to save
them.
Codex does not run a hook until it is trusted. Review the hook once inside Codex; until then it sits inert and commands run untrimmed. Connect writes the hook against its stable binary path, so a Connect upgrade does not invalidate that review.
Commands Anyray does not recognize are passed through untouched, and the rewrite only ever wraps your command, never edits it. Trimming happens on Codex's own machine; the gateway still sees metadata only.
A trimmed output ends with a footer naming a full copy in ~/.anyray/hook-tee. Codex can read it
with cat, or pass its 16-character id to anyray_retrieve. Codex's workspace-write sandbox
blocks that folder, so there the copy goes to anyray-hook-tee-<uid> in your temp directory,
owner-only and used only when your account owns it. The anyray skill tells Codex to fetch those
copies with anyray_retrieve by id, never by path. It has its own 128 MiB budget, and the OS may
clear a temp directory sooner than Connect would, so those copies can expire early. Uninstall
removes it. If Connect cannot write a copy anywhere, as under the read-only sandbox or with
ANYRAY_HOOK_TEE=off, Codex gets the output unchanged.
Connect measures this lane on every seat and reports it in its health checks as three informational
codex rows over the last 7 days: source_trim names what limits the lane (trims_home,
trims_via_temp, tee_partly_blocked, tee_blocked, tee_off, no_digest or no_outcomes),
source_trim_counts counts rewrites and runs by outcome, and source_trim_kib gives the output
sizes before and after in KiB. Counts and sizes only, never a command or output.
The same hook reminds Codex to batch its reads. It is on by default. To turn it off, set
clientToolPolicies.readBatchCodexNudge to false in the profile; the hook reads the switch on
every command.
{
"clientToolPolicies": {
"readBatchCodexNudge": false
}
}
Two single-range reads in a row (sed -n, cat, head, tail, nl) add a
short note to Codex's context naming tools.mcp__anyray__anyray_read_batch and how
sed -n "a,bp" maps to offset and limit. It can repeat after six more such reads, up to three
times per session. It fires only when Codex has the Anyray MCP server registered with
anyray_read_batch approved, every running Anyray MCP server serves ranged batch reads, and Codex
uses a ChatGPT sign-in. On an API key it stays quiet: the gateway may translate the request for
another provider and move the note into the system prompt, which costs a cache rewrite. Servers keep
the switches they read at start, so a profile change takes effect after the Codex session restarts.
The note never approves or rewrites a command, and the hook keeps only a hashed session ID and
counts. clientTools.readBatchCodexNudge in doctor --json and status --json shows the switch.
On Windows, Connect does not install this hook and removes one an older Connect left. The trim does not run there yet, so the hook would only add a process launch before every command.
Anyray does not register a Codex PostToolUse hook. Codex can only replace a finished tool result
by rejecting it, which would report a command that already ran as failed. An older Connect
installed one; apply removes it.
Claude Code source trims
Managed Claude settings include the source hook in both credential lanes. Deferred SSO and an explicit fleet binary use the command hook. Without a binary, Connect emits a native HTTP hook with the client key read from the host environment, never embedded in the fleet file.
Managed command hooks require current-session retrieval proof and verify the actual handle before emitting a replacement. They keep original output on failure or after two seconds. HTTP hooks currently stand aside because their event cannot prove session retrieval. Existing content-mode controls still govern gateway capture.
HTTP hooks also cannot invoke Claude's apiKeyHelper. If the credential exists only in that
helper, authentication fails open to original output. Local managed --write selects a durable
Connect binary automatically; fleet --print uses only the binary explicitly supplied by IT.
doctor and status --json expose sourceHook: installation is command, http, missing, or
unknown; retrieval is verified, not_seen, or unknown. A recent report appears separately on
the console's Agents row. Missing reports and older gateways never mean a hook is absent.
One experiment lives under hookPolicies in the existing Connect profile,
~/.anyray/connect.json (or the legacy ~/.anyray file). Merge this field into the profile;
keep other settings. No environment variable enables it.
{
"hookPolicies": {
"greenCollapse": false
}
}
Set it to true only while benching it. Each hook reads the switch afresh; setting it back
to false stops it on the next invocation. It defaults to false. Connect ignores any other
key under hookPolicies, including rereadStub, which an earlier Connect read.
What the MCP server advertises uses a separate clientToolPolicies subtree in the same
profile, because those change the bytes in every prefix rather than trimming hook output.
The switches are read when the MCP server starts and apply to every client on the machine.
readBatch controls anyray_read_batch (batch reads).
Set it to false to turn the tool off. doctor --json and status --json report
it under clientTools:
readBatchAlwaysLoad defaults to true. It puts batch read in Claude Code's
main and subagent tool lists without a ToolSearch call. Set it to false
to restore the previous deferred tool definition on the next client restart.
doctor --json and status --json show this switch as
clientTools.readBatchAlwaysLoad.
readBatchInstructions defaults to true. It applies only to a server started as
__anyray-mcp-server sdk (a Claude Agent SDK app). That server reads the Connect profile and
lists only anyray_retrieve, anyray_recall and anyray_read_batch. While batch read is
listed, its startup instructions tell the model to read located ranges with one
anyray_read_batch call. Claude Code and other clients never get that line. Set it to false to
drop it on the next app start. doctor --json and status --json show it as clientTools.readBatchInstructions.
readBatchRanges defaults to true. Set it to false to restore the complete-file
tool definition. With ranged reads on, the advertised schema takes reads only; request a complete file as a range from line 1. The server still answers a paths call from a client that listed the complete-file definition before the switch took effect.
readBatchNudge defaults to true. With ranged batch read on the local stdio MCP
server, Claude Code's PostToolUse hook reminds an agent after two consecutive
single-range reads (a ranged Read, or a Bash sed -n, cat, head, tail or nl print). It can
repeat after six further consecutive reads, up to three times per agent. A search whose
result lists 2 to 300 line-numbered hits in the workspace (grep -n, rg -n) gets a reminder
to read the ranges the agent needs in one call, up to twice per agent; after it, the first
single-range read gets the reminder instead of the second. Explore and
general-purpose subagents also receive a short hint at startup. Set it to false
to stop these reminders and the subagent hint; clientTools.readBatchNudge in doctor --json and
status --json shows the switch. The hook stores only hashed session and agent IDs
and counts locally.
searchContextHints defaults to true. While every running Connect MCP server serves
Symbol context, the startup hints end with one sentence naming
anyray_search_context, and a search with mostly TypeScript or JavaScript hits gets a reminder
to fetch those definitions by name. Set it to false to keep the tool with the batch-read hint
text. clientTools.searchContextHints in doctor --json and status --json shows the switch.
readBatchMainHint defaults to true. The main agent gets a work-mode hint at startup: trace
the workspace itself in batched reads instead of spawning Explore subagents, and never re-read a
range a subagent reported. It is a hint only: every agent type, Explore included, still runs. Set
it to false to turn it off. It is not sent when Claude Code runs with --max-turns (or the
Agent SDK's maxTurns) below 80: the cap counts only the main agent's turns, and the mode moves
subagent work into them.
readBatchCodexNudge defaults to true. It is the same reminder for Codex, read by
Codex's hook on every command (Codex source trims).
webFetchRefusalNote defaults to true. Claude Code's WebFetch summarizer will not quote long passages verbatim. When its reply declines to (for example "I can't
reproduce the full README verbatim"), the PostToolUse hook adds one note to that result: answer
from the facts, section names and paraphrase it returned instead of fetching the page again. It
fires once per page per agent, and never on a reply that is not such a refusal. Set it to false to turn it off; clientTools.webFetchRefusalNote in doctor --json and
status --json shows the switch. The hook stores only hashed session, agent and page keys.
constantContext defaults to false. When on, every Claude Code compaction ends with one note
that names the transcript from before it as a ctx_k_ handle. The agent reads it back with
anyray_retrieve and a grep or a line range of up to 300 lines, so a detail the summary
dropped is looked up instead of redone. The transcript is Claude Code's own session file and stays
on this machine; only the slices the agent reads back are sent, like any other tool output. It
needs the local stdio MCP server.
The switch also gives Claude Code an anyray_record_state tool. The agent records its goal,
decisions, key files, exact facts, open questions and next steps as it works, and after each
compaction the note shows that state back, at most 8,000 characters of it. Connect stores
nothing: the state is replayed from those calls in the session file.
Connect does not change when Claude Code compacts; set that yourself with /autocompact.
clientTools.constantContext in doctor --json and status --json shows the switch.
clientTools.readBatch | Meaning |
|---|---|
off | The switch is set to false. |
invalid_policy | The clientToolPolicies subtree is malformed, so its switches fail closed. |
enabled_ranged | On, and this platform serves file ranges by default. |
enabled | On, and readBatchRanges is false, so it serves complete files. |
unsupported_platform | On, but this operating system has no confined reader. |
unsupported_architecture | On, but this CPU architecture has no confined reader. |
native_binding_unavailable | On, but the native reader failed to load (Linux and Windows; the macOS reader needs no native binding). The MCP startup log records this state. |
The MCP server sends that state on its five-minute heartbeat. Your admins read
the state on the Agents card and GET /admin/v1/keys/:id.
The heartbeat counts each Anyray tool call as an attempt, including refused calls.
Its separate readBatch report counts successful and failed batches and the
entries returned. A partial batch counts only the entries it returned. A call
can start before one heartbeat and finish after it, so attempts and completions
need not match within a reporting window.
Anyray operators can inspect these counts per agent and deployment. Reported outcomes may cover fewer hosts or less time than the attempt counts. An older host leaves outcomes unreported; a supporting host reports zero when idle. Failed heartbeat delivery can lose counts. These counts establish usage; savings use separate evidence. Only tool names, states, timestamps and integers leave the host. No file path, argument or result is sent. Older gateways ignore the added fields.
teeFooterIdRoute controls how anyray_retrieve's description tells Claude Code to
recover a hook-tee footer, so it applies on that one lane. On, it says to pass the
footer's 16 hex characters as the handle rather than reading the path, and it names the
inline … [12 line(s) omitted by anyray · lines 40-51] marker so an agent can ask for
exactly that range. Reading the path stays as the fallback for a client that has no
anyray_retrieve to call. Set it to false to serve the wording used before that change,
which sent the agent to the file path first. The switch exists so the sentence has an off
direction; the on wording costs five more tokens than the off one.
A malformed clientToolPolicies subtree reads as off rather than falling back to the
defaults, so a typo in an opt-out is never overridden. That applies to the whole subtree:
one type error turns every switch in it off, not just the one you mistyped.
doctor --json and status --json report which wording a machine resolved as
clientTools.teeFooterWording: id_route or legacy. A Connect too old to know the
switch omits the field rather than reporting legacy.
These describe the switches, not a running session. Each MCP server resolves them once at startup, so a client already running when you change one keeps its previous answer until you restart it. Opt-in repairs older hook settings for future sessions.
| Switch | When it can hide output |
|---|---|
greenCollapse | Bash ran a recognized test, lint, build or type-check command, reported numeric exit code zero, and printed a recognizable summary. Keeps the runner's summary lines, warning paragraphs, and all stderr. |
Green collapse recognizes commands such as go test, golangci-lint run, pytest, vitest,
jest, tsc, cargo test, make test, and eslint, including common package-runner wrappers.
It hides only recognized progress rows. Benchmark, verbose, debug, coverage, duration and reporter
flags decline; unaccounted rows such as asset-size tables also decline.
It accepts cd … && and chains of recognized runners, but declines pipelines, ||, shell
program strings and unsupported syntax. Nonzero, missing, conflicting or interrupted exit status,
or any failure marker (including FAIL, panic, Error, Traceback, not ok, or an assertion diff),
declines to the shipped hook. No recognizable summary also means no collapse.
Every decline preserves the existing digest pipeline.
Claude Code 2.1.257 does not send numeric exit status. Green collapse therefore stands down on that client, even when enabled. Synthetic numeric-status tests establish guard behavior, not live savings.
Green collapse requires the registered Anyray MCP server and a successful anyray_retrieve call
in the bounded main transcript tail,
a successful local tee, and a replacement shorter than the original. Restricted subagents and
unknown contexts decline to the shipped hook. The marker contains retrieve ctx_…; call anyray_retrieve
with that handle for the bytes. These handles resolve through Connect's existing local tee
reader, not the gateway stash. The original stays on the machine under the tee retention rules
above. No extra gateway optimization request runs for a collapse.
Missing session identity passes through. Records are bounded to 64 sessions, with a 24-hour idle expiry checked on use. Compaction and session end remove a session's handles; they do not delete recovery bytes still referenced by transcript markers.
doctor --json and status --json expose hookPolicies and hookPolicyMetrics. Metrics include
greenCollapses, retrievesAfterCollapse, retrievalMissesAfterPolicy, completedWindows,
censoredWindows, observationGaps, and observationGap. Exact-handle retrievals are observed
for the next five main-transcript tool calls. Subagent calls do not advance these windows.
Compaction or session end before five calls censors that window. An observation gap invalidates
benchmark results. Recovery clears stale session history automatically; observationGaps
preserves the evidence loss after the transient signal clears.
The switch stays off until paired runs beat pass-through beyond the 53% noise floor and the third quartile still saves. Include retrieval calls, cache writes, and long-tail turn counts in the comparison. Fewer output bytes alone do not establish a session saving.
Per-tool switches
The desktop app's Tools tab gives each installed tool a switch, and a switch ends in the same
state as anyray-connect --tools <id> or --tools <id> --revert. Tools that are not installed
stay in a folded list without a switch.
Turning a tool off records your choice. Sync and repair leave it off; only a lock puts it back.
The switch shows what you chose. A tool you turned on stays on when its routing breaks, and the row says what is wrong. A tool that is off shows no health status. Claude Code's settings-file control works whether it is on or off.
Claude Code settings file
In Tools, expand Claude Code and choose Change beside Settings file. The default
is ~/.claude/settings.json. Save an absolute path or a path beginning with ~/. When Claude Code
is on, Connect removes its setup from the old file and sets it up in the new one in the same step,
also when your admin made Claude Code Required. Use default restores the default path in the
editor; choose Save to keep it.
If the new file cannot be set up, the path stays saved and the dialog says so. Turn Claude Code on again, or choose Sync on its row when it is Required.
Connect uses the selected file for setup, refresh, diagnostics and revert. Saving the path does
not copy settings from another file. Your launcher must load the dedicated file with Claude's
--settings option. MCP registrations and skills remain shared across Claude sessions.
Connect also loads this file for Claude Code hooks and approvals in JetBrains and Windsurf.
doctor --json and status --json report claudeCodeSettingsPath on the Claude Code row.
claudeCodeSettingsEditable says whether your tool policy permits editing: Required does, a
device-managed seat whose policy leaves Claude Code out does not. These fields describe
Connect's configuration; they do not confirm which file a running Claude session loaded.
What sign-in turns on
Signing in to the desktop app, or pasting a personal link into it, turns on the Required tools and keeps every tool that was already on. Every Available tool starts off until you flip its switch, and policy sync leaves it off. The Tools tab opens after sign-in and names the account, team and gateway it used.
| Tool is | After sign-in |
|---|---|
| Required (locked), installed | On, read-only |
| Required (locked), not installed | Turns on by itself once you install it |
| Available, installed | Off until you turn it on |
| Available, installed later | Off; the app tells you once that it found the tool |
Enrolling from a terminal or through MDM still turns on Available tools by itself.
Who can switch what
| Tool is | Unmanaged machine | MDM-managed machine |
|---|---|---|
| Required (locked) | Read-only | Read-only |
| Available (any other tier, or none) | Switchable | Switchable |
| Not enabled by the admin | Switchable | Read-only |
On an MDM machine with no saved tool policy, every tool is switchable. Claude Desktop is never switchable on an MDM machine. A read-only row says why: "Locked by your admin", "Managed by your organization's device management", or "Not enabled by your organization". A Required tool that is still off reads "Locked by your admin. Not set up yet." and offers Sync, which sets it up. After an admin un-routes an Available tool, you can turn it back on.
Pending changes and restarts
A change that needs an app to quit shows the target position with a Pending tag until the app closes (Cursor and Claude Desktop). Flip the switch back to cancel it. A switch that only needs a fresh start says so: "Start a new Claude Code session to pick this up" for a terminal tool, or "Restart Cursor to pick this up" for a desktop app.
The same note shows when a repair or a team sync changes the config of a switched-on tool that needs a
restart, a new session or a new shell to pick it up. doctor --json marks such a tool
pickUpPending: true. The note goes away when the tool sends a request through the gateway from this
machine, when the app restarts the tool, or when the change is older than the gateway's look-back
window. A request from the same person's other machine does not clear it. Tools whose requests the
gateway does not time (Copilot, Cursor on its own subscription) never get it. Neither does a machine
whose key is not bound to an enrolled device, or one on a gateway too old to report per-device
requests: those only see the note after a switch.
A desktop app that is running gets a Restart button (Cursor, Claude Desktop, and VS Code for
Copilot Chat and Copilot CLI), where Connect can reopen it. Clicking it gives the consent that
--quit-apps gives on the command line. An app that refuses to quit keeps the Pending tag.
Clicks queue and run one at a time; a waiting row reads "Waiting". Turning on Claude Desktop asks for confirmation first.
Terminal tools
Turning off Claude Code or Codex while your shell is set up for Anyray leaves the tool routed when it starts from a terminal; the row says so. A configuration piece two tools share, such as the Copilot MCP entry or the Claude Code MCP approvals, stays until the last tool using it is off. Your enrollment and key are never removed by a switch.
What the admin sees
Each seat's routing check reads the switch back.
| Tool | Console shows |
|---|---|
| Available, turned off | "Turned off by the developer" |
| Not in the list, turned off | "Off" |
| Never turned on | "developer has not opted in" |
The seat reports a switch change right away; if the gateway is unreachable, the next full doctor
run reports it.
Tool health
Claude Code's per-session route shortcuts install through the same tool setup in the CLI and desktop app. See choose a Claude session route for commands, supported shells and managed-policy conflicts.
Every enabled tool (one Connect set up and you have not turned off) gets one summary in the desktop app. Tools that are off or not set up show none and never notify.
On Tools, each configured tool has one row with its own verdict, controls and evidence. Select its name to expand details. Expansion and keyboard focus stay with that tool across report updates. A changing tool cannot retain its previous completed reading. Missing health stays unconfirmed; another tool's traffic or a global key timestamp cannot complete its check.
| Summary | Meaning |
|---|---|
| Working | The gateway has seen this tool's own traffic, and its hook has run where it has one. |
| Ready | Every check Connect can run passed, but Connect has not yet proven the tool's own traffic or hook. The Traffic detail says what the gateway has seen. |
| Needs attention | A problem Connect could not fix. A short note says what to do. |
| Fixing | Connect found a problem and is repairing it. No action needed. |
The Traffic detail shows what the gateway has seen from this tool in the last 7 days:
| Traffic reads | Meaning |
|---|---|
| Seen | The gateway has recorded requests from this tool. |
| Not seen yet | The gateway answered and counted none. |
| Checking… | No gateway answer yet: it just started or the check is still running. |
| Can't check right now | The gateway can't report traffic at the moment: it is unreachable, rejected the request, rate-limited, or its traffic read failed. |
| Your gateway can't report this yet | The gateway is older than this check. The console's Health column shows Gateway update needed so the admin knows to upgrade. |
Ready can show Seen: requests are arriving, but the tool's hook is not proven yet. For Copilot in its default seat mode, Seen means Anyray optimized its calls.
The MCP and Hooks details come from the same checks as the summary, so a detail never disagrees with it. Connect leaves out a row that does not apply to the tool.
| MCP reads | Meaning |
|---|---|
| Set up | The retrieval server is registered. |
| Won't start | It is registered but does not start. |
| Not approved | The tool has not approved the server's tools. |
| Needs repair | The entry is out of date or another install owns it. |
| Missing | The entry is not there. On an MDM-managed Claude Desktop: the organization's policy does not serve it. |
| Not supported | The tool has no MCP support. |
| Set up on first launch | The tool has never been opened. It creates its config on first launch. |
| Can't confirm | Claude Desktop's managed policy could not be read, so Connect cannot tell whether it serves the server. |
| Hooks read | Meaning |
|---|---|
| Set up | The hook is installed and nothing blocks it. |
| Not running | The hook is installed but has not run. |
| Turned off on this machine | The tool's settings disable hooks. |
| Blocked by a machine policy | A policy drops Anyray's hook. |
The person's Machine status tab on Users shows the same summary per tool. A problem Connect has not fixed in about 2 hours, or one only an admin can fix, reads Warning there. A rejected sign-in reads Error.
The checks: the tool's config points at the gateway, the retrieval MCP server is registered and
starts (Connect launches it briefly and asks only for tools/list), the tool's own hook has run, and
the tool's own traffic reached the gateway. Connect runs the same repair as Troubleshoot once per
problem. It sends one system notification only when you must act.
Every Needs attention row ends in an action. When Connect can still fix it, the row says
Click Troubleshoot. When Troubleshoot did not fix it, or only an admin can, the row offers
Copy report and asks the user to send it to their admin. The report carries the context behind
the cause (which policy, which config path, what Connect already tried). The report opens with an issues block: per tool, the problem, the
evidence Connect read (the policy file and setting, the config path, or the server command), what
self-heal tried, and the same fix as the table.
| Cause label | Shown when | How to fix |
|---|---|---|
| Sign-in rejected | The gateway refused the key. | User clicks Troubleshoot. If it returns, check their access in the console. |
| Not routed | The tool's config does not point at the gateway. | Connect re-applies it. If it reverts, routingOverrides names what manages the config. |
| Retrieval missing | The retrieval MCP server is not registered. | On a managed Claude Desktop, mcp.reason not-delivered means the organization's policy does not serve the anyray server: upgrade the gateway or turn claudeDesktopRetrievalMcp on. Otherwise Connect re-registers it. If it stays, see mcp.reason: a foreign anyray entry, an unreadable config, or a temporary install path. A foreign entry reads foreign (someone else's server), foreign-tail (Connect's own server with an argument it never wrote: drop the argument) or foreign-remote (a URL server named anyray: rename or remove it). Connect leaves all three as found. |
| Retrieval won't start | The server is registered but does not start. | Connect re-registers it. If it stays, the binary at mcp.command is missing or blocked. |
| Retrieval not approved | The tool has not approved the server's tools. | Approve anyray_retrieve in the tool's managed settings. |
| Hooks blocked | Something blocks the tool's hooks. | Remove disableAllHooks from Claude Code's managed settings, or allow Anyray's hook. |
| Bypassing Anyray | The tool talks to its provider directly. | Remove the base-URL override: a project .claude/settings.json, CLAUDE_CONFIG_DIR or --settings. |
| Hook never ran | The hook is installed but has not run. | Connect re-registers it. If it stays, a policy or project override blocks user hooks. |
anyray-connect doctor prints one line per tool with its summary. doctor --json and Copy support
report carry tools[].health ({ "summary": "working" }, or { "summary": "needs_attention", "cause": "...", "repairable": true }) and selfHeal, what the app repaired on its own.
Traffic evidence comes from GET /connect/self-check. The fleet sees
the summary as the tool_health check, one per tool: ok with the summary, needs_attention
with the cause, or needs_admin when only an admin can fix it. A ready tool reports
ready_traffic_none, ready_traffic_seen, ready_traffic_pending or ready_traffic_unavailable;
an older Connect sends a bare ready. It is never scored in compliance. It sets each person's status
on Machines, and the console Tools page shows it in the Health column. For a Ready
tool both add the traffic detail: Ready, no traffic yet, Ready, traffic seen, Ready,
checking traffic, or Ready, traffic can't be checked. A seat on an older Connect shows plain
Ready. A person who reads Error gets their own row in the sidebar Errors list.
Personal LiteLLM key
When your organization sets a personal LiteLLM URL, doctor checks that this machine holds a key for
it. Each state has its own error class in the health report:
| Class | Meaning |
|---|---|
upstream_key_missing | The organization names a personal URL and this machine has no saved key. Not reported under --org, --subscription or a lane your admin assigned. Run anyray-connect --upstream-key-stdin. |
verify_upstream_key_rejected | The upstream rejected the saved personal key. Run anyray-connect --upstream-key-stdin with a current key. |
Background services
On a default seat, Connect installs no background service at all: a gateway key lives 90 days, so nothing can expire under a running tool. A small unprivileged service appears only where your organization issues fast-rotating keys, or on a seat used solely for SDK and shell work. It sends credential data only, and never your prompts or replies. Per-platform service labels, unit-file paths, and how to opt out: Key renewal.
anyray-connect doctor --security-review prints the service names, unit-file paths, outbound
hosts, and the self-updater's full footprint (the release URLs it fetches, the paths it
writes, and the two commands it runs on a freshly downloaded binary) that your security or
endpoint team needs to allow-list. It is derived from the installers themselves, works
offline, needs no key, and says nothing about your traffic. Where that sheet and this page
disagree, trust the sheet.
curl -fsSL https://app.anyray.ai/connect.sh | sh -s -- doctor --security-review
Service keys
An agent host (a CI runner, a bot, a scheduled SDK job) has no person to sign in, so the console mints it a service key instead. Connect runs on that host with the key in the environment and the same headless apply a developer uses:
export ANYRAY_CLIENT_KEY=ark_svc_… # from the host's secret store
curl -fsSL https://app.anyray.ai/connect.sh | sh -s -- --gateway <origin> --yes
Detection picks the installed tools; add --tools shell-env to write only the SDK environment.
What changes on this lane:
- Identity and team come from the key.
--userand--teamare ignored, with a note saying so. - There is no enrollment cert, so nothing refreshes: no background service, no LaunchAgent or scheduled task, and the key is never re-minted on the host. Rotate or revoke it from the console.
- No remote-environment offer, and
--projectis refused: a committed repo config gets its key throughprint-key, which mints from a cert the host does not have. - Tool configs, the retrieval MCP server, and the profile are written as usual, so
doctor,status, and the MCP server all find the key.
doctor titles the credential section Service key (agent), doctor --json and
status --json carry keyKind: "service", and the host sends no seat health report: an agent
is not a seat. A --json apply marks its applied event with the same keyKind.
Desktop browser sign-in
The installed Anyray Connect app uses the macOS authentication session or an
installer-registered ai.anyray.connect browser callback on Windows and Linux.
GUI sign-in opens no localhost listener. Keep the app running until enrollment
finishes; if browser policy blocks the return to the app, ask your administrator
to permit the installed app's callback handler and retry. Standalone CLI sign-in
continues to use a temporary loopback listener.
Admin access
The admin guide covers the CLI, Connect app and Claude setup. Command list.
Gateway and credentials
admin resolves --gateway, then $ANYRAY_GATEWAY_URL, the saved Connect gateway, and finally
http://localhost:8787. Credentials belong to the URL's origin. Remote gateways require HTTPS;
loopback HTTP works for local development. Redirects are refused.
admin login --token-stdin trims stdin and chooses the login lane by prefix:
| Input | Action |
|---|---|
| Nothing (empty after trimming) | Refuse locally with token_missing; nothing is sent. |
aak_ | Verify and store the admin API key. |
hs1. or sso1. | Verify and store the pasted session. |
ark_, adt_, enl_, or sk- | Refuse locally with not_an_admin_credential. |
Bearer (any case) | Refuse locally with not_an_admin_credential. |
| Anything else | Exchange the admin token for a local session, then verify it. |
Credentials never go on argv. Browser login requests an admin session and verifies it through
GET /admin/v1/me. Both lanes save only after verification succeeds.
~/.anyray/admin.json stores version 1 with a gateways map keyed by origin. Each entry has
kind (sso, local or apikey), credential, and the verified identity's sub, role and
scopes and canWrite; exchanged sessions also have expiresAt.
Writes are atomic and mode 0600. The raw admin token and browser poll secret are never saved.
Save and logout remove stale admin.json.tmp-* files under the credential lock, even with no
stored credential. Browser opening never delays polling; use the printed URL if it fails.
Sign-in and local status output
admin login --json prints NDJSON. Browser sign-in first emits an admin-sign-in object with
browserUrl, userCode and expiresAt. It then emits one result object with state.
Success also includes role, sub, gateway (the origin), session expiresAt (null for an API key),
and canWrite. Token-stdin login emits only the result. Diagnostics go to stderr; neither stream
contains the credential or poll secret.
admin status --json reads local files without a network request. It always returns signedIn,
gateway, kind, role, sub, expiresAt, expired, canWrite and
mcp: { claudeCode, claudeDesktop } (the two Claude flags) and mcpRegistrations.
The latter has claudeCode, claudeDesktop, codex and cursor keys, each one of these:
| State | Meaning |
|---|---|
registered | Connect's entry for this gateway is in the config. |
not_registered | The client is installed and its config has no anyray-admin entry. |
client_not_installed | The client is not installed and its config has no anyray-admin entry. |
mcp_name_conflict | An anyray-admin entry that connect did not write. Install and logout leave it. |
mcp_other_gateway | Connect's entry for another gateway. Install and logout leave it. |
config_unreadable | The config cannot be parsed, or its servers key is not a table. |
What the config holds wins over whether the client is installed. Missing identity fields are null. An expired entry remains
signedIn: true. Login sets canWrite when a verified scope matches x-required-scope on a
non-GET /admin/v1/ operation in the gateway's /openapi.json. If the spec cannot be read,
login succeeds with canWrite: false. Status reads this saved value; older entries default to
false. MCP flags count only owned registrations for this gateway. skill is installed,
missing, not_present (no ~/.claude) or name_conflict (a skill connect did not write).
The Connect app requires a saved gateway connection for admin access. Its Admin card uses the same sign-in and status functions as the CLI. Transient poll failures keep its pending sign-in for the next poll. A received session is revoked best-effort if verification or saving fails in either login lane. App admin requests have a 2-second HTTP timeout so polling, verification, spec loading and failure revocation fit within the app's 15-second command budget. Sign-out stays available for a stored session even after another sign-in fails.
API requests
When stdout is not a terminal, responses marked x-returns-secret or x-returns-content need
--output <file>. Without it the command returns output_required (exit 2) before any
credentialed request, also with --all. A path the spec does not list returns
unknown_operation, a spec without the secrecy markers returns gateway_upgrade_required, and an
unreadable spec returns spec_unavailable. A terminal is unchanged.
| Flag or argument | Meaning |
|---|---|
| Method | GET, POST, PUT, PATCH or DELETE, case-insensitive. |
| Path | A relative resource such as routing, or /admin/v1/routing. No other root, scheme, dot or empty segment, backslash, query or fragment. |
--body @file | Parse a file as JSON. |
--body - | Parse stdin as JSON. |
--query k=v | Append one encoded query parameter. Repeat for more. |
--json | Print only the successful response body. A gateway refusal prints one error object instead (see Errors and logout). |
--output <file> | Stream a successful raw response to a new mode 0600 file without a size limit; print bytes written and path to stderr. Existing paths are refused before any request. |
--force | With --output, replace an existing file. The response goes to a new 0600 file beside it, renamed over the target only on success, so a failed request keeps the original. A directory is refused. If the rename fails, output_not_replaced names the new file, which keeps the response. |
--all | GET only, without --output. Follow next_cursor through every page of a list and print each row; with --json, one row per line (NDJSON). Sends the operation's largest limit unless you pass one. Stops after the page that reaches 10,000 rows (the last request asks only for the rows left), or after 1,000 pages, with warning[page_cap] and the cursor to resume from. Rows the gateway reports as skipped end in warning[rows_skipped] with the count. |
A non-JSON response, such as a .mobileconfig, needs --output. Without it the command fails
with binary_response and names the media type. --all refuses an operation with no cursor
parameter, or a sort that has none, with not_paged before printing a row.
ops reads the public OpenAPI spec and your scopes. Its filter matches operation id, path,
summary or scope without case sensitivity. whoami --json prints the /me body alone.
describe <operationId> or describe <METHOD> <path> reads the same spec, with no sign-in, and
prints the operation's scope, its parameters and its body fields. Each field row shows the path
(config.strategy.mode), type, whether it is required, and its allowed values. A body that takes
several shapes lists each option. A schema that contains itself, such as a routing target, prints
once and is then named (config.targets[] same fields as config). --json prints the object the MCP's
anyray_admin_describe_operation returns.
Human output strips terminal controls and neutralizes Unicode bidi overrides and line separators.
JSON output escapes these characters and preserves their values when parsed.
Secret input and links
anyray-connect admin secret set provider_key <slug> [--stdin] [--json]
anyray-connect admin secret set web_search_key <brave|tavily|exa> [--stdin] [--json]
anyray-connect admin secret set scim_token [--stdin] [--json]
anyray-connect admin secret set spend_connector_token <cursor|devin> [--stdin] [--json]
anyray-connect admin secret set spend_connector_token github_copilot --scope organization|enterprise --slug <slug> [--stdin] [--json]
anyray-connect admin secret open sso_portal [sso|dsync] [--json]
anyray-connect admin secret open enrollment_link [--json]
set opens a one-time page on 127.0.0.1, valid for ten minutes. It checks access before
opening the browser, saves the value, then verifies the non-secret configuration view.
--stdin reads the value to EOF and skips the browser. A provider key replaces the current default
key and keeps other named keys; providers with several credential fields need the console. A new
connector token keeps a paused connector paused. On a gateway that advertises
connector-token-update, that holds for a pause made while the page was open too.
After an admin-token change the gateway cannot read stored connector tokens; set then reports
connector_credentials_unreadable: re-enter them under Settings → Providers → Connect read-only tools.
open shows a local page with an Open link button. Only the click creates the link, then
the browser goes there. Loading the page creates nothing.
The first browser to load a page keeps it. When another browser or process loads it too, the
page refuses every later submit and the result is contested: nothing was saved, or no link
was created. If the contest arrives while a save or link is in progress, saved is true:
rotate the key, or revoke the link, which is never shown.
Neither command prints the page address, credential, link or gateway response body.
--json prints only state and target, plus verified for ok, saved for contested, or
HTTP status for gateway_error. Human output reports saved and verified, saved and not verified, or opened.
browser_unavailable means the browser could not open; use --stdin for input.
An unused page reports expired. These states and contested exit 1; unsupported_target exits 2.
Admin MCP
admin mcp install registers anyray-admin in the Claude clients present. Sign in first.
Repeat --client claude-code|claude-desktop|codex|cursor to target specific clients, including
clients not yet detected. Codex and Cursor require --client; the default stays Claude only.
If neither Claude client is detected, the bare command returns no_client without writing.
--client codex returns client_not_installed and writes nothing until Codex has run once
($CODEX_HOME, default ~/.codex, exists): Connect detects Codex by that directory.
| Client | Config and entry |
|---|---|
| Claude Code | ~/.claude.json, mcpServers.anyray-admin |
| Claude Desktop | Its active claude_desktop_config.json: ~/Library/Application Support/Claude-3p/ on macOS, %LOCALAPPDATA%\Claude-3p\ on Windows, or ~/.config/Claude/ on Linux; mcpServers.anyray-admin |
| Codex | $CODEX_HOME/config.toml (default ~/.codex/config.toml), a plain [mcp_servers.anyray-admin] table outside Connect's blocks |
| Cursor | ~/.cursor/mcp.json, mcpServers.anyray-admin |
The anyray-admin skill is written to ~/.claude/skills only when Claude Code is targeted,
detected, and its MCP registration succeeds, never through team policy. Without Claude Code,
--client claude-code still registers the MCP in ~/.claude.json but reports the skill as
not_present and never creates ~/.claude. A skill connect did not write is left as is. Logout removes connect's copy once no Claude Code admin MCP entry still uses it.
Writers preserve other servers and settings, including the retrieval server anyray and org
relay anyray-connectors. Connect owns an entry that runs admin mcp; in Claude Desktop, Codex
and Cursor the entry must also carry connect's --anyray-owner= argument (any
--anyray-owner=admin-mcp- version in Codex and Cursor), so a hand-written wrapper with the same
arguments is not taken. A foreign entry returns mcp_name_conflict; an
owned entry for another gateway returns mcp_other_gateway. Both leave the file unchanged. Invalid or unreadable configs
return claude_config_unwritable, claude_desktop_config_unwritable, codex_config_unwritable
or cursor_config_unwritable. Each client installs independently, so a refusal does not block
other clients.
Installation requires an existing durable binary. Its arguments include the gateway origin;
credentials stay in admin.json. An owned entry with no --gateway, or more than one, counts as
another gateway's. Routing apply, org sync and seat revert leave the admin entry in
place. Only admin logout removes it, and only when owned by connect for this gateway. Logout
removes Codex's saved tool approvals under the entry too; a reinstall that moves the binary keeps
them. A Connect sync that rewrites config.toml at the same moment can undo an install or a
logout. admin status --json shows the result, and running the command again settles it.
--json prints installed booleans for this gateway after the run, clients outcomes, and skill.
Both installed and clients always have claudeCode, claudeDesktop, codex and cursor keys.
Client outcomes are ok, not_present, not_requested, or a refusal state. The resident returns
the same fields with an overall state; the Admin card shows each client's outcome.
Installation adds no write approval rule. Ordinary Connect setup does not register the admin MCP.
| Tool | Input |
|---|---|
anyray_admin_whoami | None. |
anyray_admin_list_operations | Optional filter and exact resource. |
anyray_admin_describe_operation | operationId. Returns parameters and the resolved JSON body schema. |
anyray_admin_read | operationId, optional pathParams and query. GET only. |
anyray_admin_write | Same inputs plus optional body. Non-GET only. |
anyray_admin_secure_input | A fixed secret target and its provider or connector fields, as above. Local MCP opens a browser page; remote MCP returns a console link when available. Never supply the credential. |
anyray_admin_open_secret_link | target: sso_portal with optional intent: sso or dsync, or target: enrollment_link. Local MCP only. The link is created when the person selects Open link; with no click in about 50 seconds it returns expired. |
Calls use operation ids from the spec. callable: false rows carry a refusal reason:
gateway_upgrade_required when the spec lacks returns-secret or returns-content, or when
a non-GET operation lacks the accepts-secret feature. Reads work without accepts-secret.
returns_content marks an operation with x-returns-content; secret_input_refused marks one
with x-accepts-secret; returns_secret marks one with x-returns-secret.
The exception is
v1_put_routing_references,
callable when the spec has the accepts-secret-references feature and marks it. Routing reads keep
their credential references so they save back unchanged. A guardrail credentials record whose keys
the spec does not declare stays [redacted]; change that routing in the console. A body holding a
credential value comes back secret_input_refused, and so does a body holding [redacted], which
would overwrite the stored value. v1_put_routing stays refused.
For supported targets, secret_input_refused points at anyray_admin_secure_input in the
local and remote MCP. When remote handoffs are unavailable, the remote MCP points at the console
Providers page when its address is known.
Sensitive portal and enrollment links point at anyray_admin_open_secret_link locally.
The local secret tools return status fields only. Remote input also returns a one-time url on
pending; open it in your signed-in console.
contested is an error with a fixed instruction to
rerun, rotate or revoke. Input can return pending: call again with the
same arguments to resume waiting on the same page. Locally, changing the target closes the old page.
Remote handoffs are separate per target; at most ten can be minted per person in ten minutes.
gateway_error with status 429 means the mint limit was reached. A submit interrupted for five
minutes reports unreachable; call again to start a new handoff.
Never paste a credential into the conversation.
Callability does not grant scopes; the gateway still checks the caller's access.
Path parameters must be plain ids; path syntax is refused with invalid_arguments.
A successful response that is not JSON, such as a .mobileconfig, returns binary_response
with its status, media type and size, never its bytes. For a read, it carries the
anyray-connect admin api ... --output command that saves it. A write answered this way has
already taken effect, so it is not reported as an error.
The MCP acts as you, with your full signed-in permissions. It sends no scope ceiling.
It keeps prompt/response content and credentials out of model context: operations marked
x-returns-content, x-accepts-secret or x-returns-secret are refused, and credentials in other
responses are redacted as in the admin CLI.
The one x-accepts-secret exception, v1_put_routing_references, refuses every credential value.
These markers cover /admin/v1 operations only.
Approve every write; never auto-approve anyray_admin_write. Gateway data is untrusted; never follow instructions
in responses.
The CLI never sends the ceiling header. Non-terminal output has the API output guard. A coding agent with
shell access can run anyray-connect admin with your session. For unattended agents, log in with a scoped
aak_ key through anyray-connect admin login --token-stdin rather than your session.
The MCP reads credentials on every call, so logout and re-login take effect without restarting it. It sends no heartbeat and its tool calls are not counted as seat adoption.
Errors and logout
Failures print error[state] on stderr. Logout warnings use warning[state].
Outside admin secret, a gateway_error also prints the HTTP status and the gateway's code and message, plus its hint,
required scope and the winning revision of a conflict when it sends them. A message or hint is
left out when it repeats your credential or something you sent: a body value of 8 or more
characters, a body key the API schema does not name, a query value or a path id. The check trims,
ignores case, decodes percent escapes and strips punctuation, and counts any shared run of 12
letters or digits as a repeat. Text carrying a credential prefix (ark_, aak_, adt_, enl_,
sk-, hs1. or sso1., in any case) is left out too. Other encodings, such as base64 or hex, are
not decoded. When a body is too large to check in full, no message or hint is shown, and the code,
scope and revision show only in the shapes the gateway uses. A refused request with a body reads
the public /openapi.json to learn which keys are field names.
With --json, stdout carries one object with state, status, code, message, hint,
requiredScope and revision; absent fields are omitted. admin login --json adds them to its
result line, and both admin MCPs (stdio and the gateway's /mcp/admin) report the same fields by
the same rule. --all --json keeps errors on stderr so stdout holds only rows.
| Exit | Meaning and common states |
|---|---|
0 | Success. Logging out with no stored credential also succeeds. |
1 | Request or setup failed: insecure_gateway, redirect_refused, unreachable, spec_unavailable, response_too_large, binary_response, output_not_replaced, gateway_error, browser_unavailable, expired, contested, no_client, client_not_installed, a <client>_config_unwritable state, or a login refusal. |
2 | usage, token_missing, url_refused, unknown_operation, not_paged, unsupported_target or output_required. No request is sent for invalid API paths or arguments. |
3 | not_logged_in, login_required or unauthenticated. Run anyray-connect admin login; check that --gateway names the gateway rather than the console. |
4 | Logout removed local access but revocation returned revoke_unsupported or revoke_failed, or --all stopped at its cap (page_cap) or got a partial listing (rows_skipped). |
Logout deletes local credentials and attempts removal in all four clients before posting to
/admin/v1/me/logout for sessions. A failure in one client does not block the others or revocation.
Stdout reports one line per client plus skill and revoke; the resident returns them as
clients, skill and revoke alongside its overall state. A 200 or 401 confirms revocation;
404 means unsupported. Local cleanup completes even if the gateway is unavailable.
| Client outcome | Meaning |
|---|---|
removed | Connect's entry for this gateway was removed. |
not_registered | The config has no anyray-admin entry. |
not_present | The config file does not exist. Logout never creates it. |
mcp_name_conflict | An entry connect did not write was kept. |
mcp_other_gateway | Connect's entry for another gateway was kept. |
<client>_config_unwritable | The config could not be read or written; logout exits 1. |
An API key gets no revoke request (not_required); an owner or security admin revokes it with
anyray-connect admin api DELETE admin-keys/<id>.
Connector grants
anyray-connect connectors authorize <name> connects one of the organization's OAuth
connectors (see the organization MCP endpoint) to the developer's own
account. It binds a listener on 127.0.0.1 (a random port, for this run only), asks the gateway to start
the vendor flow with that port and a per-run nonce, opens the consent page, and redeems the
claim code the gateway redirects back with. The redirect carries the nonce's digest and the
listener accepts a code only under it, so a code from someone else's consent pushed at that
port is ignored and the run keeps waiting. stdout carries one line naming the connector and the
account. The claim code, the nonce, and the client key never reach either stream. The command
waits up to two seconds for the browser opener. If it fails or times out, the command prints
the authorization URL, including its query string, so the developer can open it by hand.
| Flag | What it does |
|---|---|
--claim <code> | Redeem a claim code the consent page showed. No listener, no browser. |
--gateway <origin> | Gateway origin, resolved like every other verb. |
In an SSH session, a dev container, or a Codespace the browser cannot reach that listener, so
the command sends no port, prints the URL to open, and asks for a rerun with --claim once the
page shows the code. The same fallback runs when no local port can be bound. A gateway that
predates loopbackPort drops it and shows the code in the browser; Connect sees that in the
reply, says so, and hands over to --claim without waiting. Consent has five minutes; after a
timeout the redirect lands on a closed port, so the hint says to copy the claim= value from the
browser's address bar. Each gateway call has a 30 second budget. Exit codes: 0 connected, 1
not connected (a failure, or any hand-over to --claim that leaves a step to do), 2 usage.
Machine maintenance commands
doctor, doctor --json and status --json report each Claude Code and Claude Desktop
organization plugin's install state.
Fleet and local supervisors call a stable, non-interactive command surface. These commands
accept no prompts and print one content-free JSON document to stdout. All except directives
write the same evidence to ~/.anyray/status.json with mode 0600.
| Command | Purpose |
|---|---|
anyray-connect reconcile | Pull org tool policy and repair managed drift. |
anyray-connect refresh | Renew credentials when due and re-assert configuration. |
anyray-connect directives | Pull and run one pending admin directive. |
anyray-connect repair | Run the complete repair pass. |
anyray-connect status --json | Print current seat evidence for scripts and policy checks. |
anyray-connect offboard | Revert managed tools, remove resident units, and clear user state. |
anyray-connect uninstall --json | Run the unprivileged user teardown and request native desktop-package removal. Add --user when an MDM script owns package removal. |
repair (or doctor --repair) also restores a missing Anyray skill through the normal skill-policy
sync. A previous sync or fetched policy must confirm that the skill belongs on this machine.
It respects your integration level and leaves reverted tools off.
It checks the files again: doctor --repair --json reports builtinSkill.kind: "ok" only when every
managed skill directory has a readable skill file. A failed download or incomplete write keeps the problem visible.
While the desktop app runs, its resident engine also attempts to restore a missing Anyray skill, even when tool routing is healthy. It tries once while that fault persists. Scheduled maintenance can retry later; an unreadable check keeps the fault visible in the app's Errors details. The fleet's Anyray skill check records failed installs and verified recovery.
Whether the model actually sees the skill is the gateway's Anyray skill loaded check, listed in Skills on your machines.
Setup and scheduled maintenance return 4 when team content could not finish writing. Successfully
written configuration stays in place; the next scheduled pass retries. Automation must wait for the
final exit code. Enrollment may emit an intermediate applied event first.
After reconfiguring tools for an update, Connect reads their configuration back. A route, key, or required integration that still reads as broken leaves reconfiguration due for another pass. Work waiting for an app to quit keeps its pending state. A check the tool cannot answer remains unverified.
Exit codes are stable: 0 healthy or complete, 1 unexpected error, 2 usage error, 3
re-enrollment required, and 4 partial with work still outstanding. stdout never carries keys,
private key material, SSO tokens, authorization values, or URLs with credentials; diagnostics
go to stderr.
doctor --json and status --json probe residue, an array describing machine-level
installer artifacts that normal user convergence cannot remove. repair reuses its doctor's
single probe in the status document it prints. A missing field means that invocation did not
probe installer residue, an empty array means the probe found a clean machine, and a non-empty
array means privileged cleanup remains. Scheduled refresh and reconcile runs omit the field;
they do not claim that the machine is clean. The only values are:
| Value | Meaning |
|---|---|
cli_package | A previous standalone Connect package is still installed. |
fleetd_package | A previous fleetd or Orbit package is still installed. |
managed_enroll_carrier | A previous managed-enrollment launch or startup carrier remains. |
foreign_binary | A Connect binary outside the desktop install is not owned by an expected package or signer. |
osquery_log_retained | Orbit osquery logs remain under the machine-level log locations. |
Connect reports these fixed enum values through authenticated seat health. It never sends the
detected filesystem or registry path. Users shows a non-empty report as Leftovers in the
person's Machine status tab, which tells IT to deploy
the current desktop installer so its privileged sweep can finish convergence. Non-empty residue counts as one doctor problem and exits with the partial
or drift code (4) on status --json and repair.
Seat health also carries the machine's time zone (an IANA name such as Europe/London), so the
console's AI activity view can show a person's hours on their own clock. Connect sends nothing else
about location.
Machine statuses on Users
The Machine column on Users shows one status per person. When several apply, the first in this table wins. An offboarded person reads Disconnected whatever their tools say. When anyone reads Error or Warning, a filter above the roster narrows it to them.
| Status | When | What you do |
|---|---|---|
| Disconnected | The person reverted Connect, on their machine or with an admin Revert. They stay listed with the date. | Click Remove in their Machine status tab to revoke their access. Their history stays. Reinstate them on Users. |
| Error | Anyray does not work for the person. A report from the last 8 hours says sign-in was rejected, or a key keeps sending traffic but Connect has not checked in for 72 hours. | Rejected sign-in: ask them to press Troubleshoot in the desktop app. Silent Connect: ask them to reinstall Anyray from their invite link. |
| Warning | Anyray works and a tool has a problem only an admin can fix, or one Connect could not fix within about 2 hours. Any failing check on a tool with no tool health report, or on the machine itself. | Open the person. Their Machine status tab names the tool and the fix. |
| No recent report | The last report said sign-in was rejected, and it is over 8 hours old. | Nothing until the machine reports again. |
| Repairing | Connect is repairing a problem less than about 2 hours old. | Nothing. |
| Active | The gateway has seen the tools' own traffic. | Nothing. |
| Ready | Every check passed. No traffic yet. | Nothing. |
| No report yet | The machine checked in but has sent no health report yet. | Nothing, unless it lasts. |
| Away | Checked in before, nothing in the last day, and no problem. | Nothing. |
| Not set up | Enrolled, never connected. | Send the invite link again. |
A person's Machine status tab shows each tool in the same words as the desktop app, who has to
act and since when, and a machine line (Connect version, whether updates wait for you to push them, a
stuck update, the last report). The raw checks sit under Technical checks, open when one fails.
/console/invite?user=<email> opens one person, and old /console/machines links land on Users.
Leftovers from an old install show in the person's Machine status tab. They do not change the status.
When Connect sent no residue field, the detail says "Leftovers: not checked by this Connect version." It does not become a clean claim or the Leftovers from an old install entry.
Desktop automation also uses persistence --adopt-app --app-path <path> for ownership
handover, persistence --release-app for removal recovery, and managed-enroll --json
for enrollment from the machine's managed settings. persistence keeps stdout empty and
returns 0 on success or 1 on failure; diagnostics go to stderr. managed-enroll --json
is silent when enrollment is skipped and emits enrollment events when it runs. The previous __anyray-persistence, __anyray-managed-enroll and
__anyray-refresh spellings remain aliases for one release.
The tray starts resident --protocol-version 1 once and sends maintenance and status
requests over stdin. Stdout carries versioned newline-delimited JSON events, bounded to
256 KiB per line. A protocol error or an unresponsive child makes status unavailable;
the tray retries with bounded backoff. Ordinary read-only status does not register login
or start maintenance.
On a fleet-managed machine, refresh skips ambient updates. The gateway
compares the seat's reported Connect version with the published release and sends an update
directive through the existing per-seat lane when it is stale. The seat runs the same
checksum-verified, smoke-tested updater as the manual update command within 30 minutes on
supported CLI runtimes. The desktop poll stages app updates and acknowledges them once the
running app is current. Other admin commands take priority while installation is pending; see
desktop updates. Off the fleet,
refresh keeps the scheduled self-update, because that tick is an unmanaged seat's only
unattended update path.
Claude Code managed policy
For Claude Code, apply and doctor inspect the machine-wide managed-settings.json. They flag
disableAllHooks: true, and flag allowManagedHooksOnly: true when the managed hooks
block does not contain Anyray's PostToolUse hook. Connect never edits IT policy: ask the MDM
owner to remove the key, or to add the credential-free hook block emitted by
anyray-connect managed --print --sso <link> --bin <path>, then restart Claude Code.
Doctor reports this as the separate claude-code / hook_policy fleet check. The failing
statuses are disabled and restricted_dropping_ours; it does not reuse the
network-liveness hook_lane check.
Connect reports recent PostToolUse hook runs in the hook_activity fleet check.
doctor --json includes each carrier's last run in hookActivity.
The gateway's key and seat admin records expose hookRuns, the Connect build and last run time per tool.
Claude Desktop configuration carry
Third-party inference gives Claude Desktop a separate profile, so the local MCP servers,
user-created skills, and user-installed plugins in the first-party profile are unread until they
are copied across. Every apply lane copies them, MDM reconciles included. On a Mac whose MDM
policy routes Claude Desktop through Anyray, Connect's background refresh copies them too, with
Claude Code sessions and Cowork tasks, even when the fleet policy holds Desktop back. MCP servers
copy right away; skills, plugins, and sessions wait for the next full quit, because Claude Desktop
rewrites those stores on exit. Elsewhere, sessions move only through anyray-connect migrate,
which reports to the person whose files they are.
Doctor prints the result under Claude Desktop profile and reports it to the fleet as the
connect / desktop_config_carry check. It is a diagnostic: no state counts against the
compliance score or the failing-check count.
| State | Meaning |
|---|---|
carried | Every local entry is in the third-party profile. |
carried_except_extensions | Every entry Connect copies is in the third-party profile, and a Desktop Extension you installed stayed in the first-party one. Connect never copies an extension. Reinstall it from Settings → Extensions while Claude Desktop is connected. |
pending | Some entries are not there yet. Connect carries them on the next reconcile, or the next time Claude Desktop is fully quit. |
nothing_to_carry | The first-party profile holds no local MCP server, user skill, or user plugin. |
not_routed | Neither Connect nor an MDM policy routes Claude Desktop through Anyray here, and nothing is queued, so no carry applies. Normal on a fleet whose policy holds Desktop back. |
unreadable | The first-party profile refused access. Allow Connect to read it, then run doctor again. |
unsupported | The host has no third-party profile (Linux). |
unknown | No evidence: a status document written before this check existed, or an inspection that failed for a reason other than access. The gateway records the same word for a doctor report from an older Connect. |
Shared pages on managed Claude Desktop
On a Mac or PC where MDM manages Claude Desktop, doctor and status print
Claude desktop shared pages: followed by the text below, and the JSON reports the state as
sharedPages (where). On Windows, a bootstrap seat that
passes the policy checks reads delivery_unknown instead of on, because Connect cannot read the
Windows bootstrap. A static Windows machine policy is read like a macOS profile.
| State | Text |
|---|---|
on | on; Claude can list and open pages shared with you. |
off | not turned on for this seat. |
gateway_too_old | needs a gateway upgrade. |
not_delivered | not delivered to this machine; your admin withholds the managed Anyray connector (content mode off, or Desktop retrieval turned off). |
delivery_unknown | could not confirm that your organization's managed Desktop policy includes the Anyray connector. |
unavailable | could not check. |
static_row_missing | not delivered; your organization's static Desktop profile has no anyray row and local connectors are off. Add the anyray row to the static profile (regenerate it with anyray-connect desktop fleet static). |
blocked_by_user | off; you turned off local connectors in Claude Desktop's settings (features.isLocalDevMcpEnabled). Turn them back on to read shared pages. |
share, beside it, reports the local anyray-share publishing connector: registered,
not_needed, missing, unresolvable, gateway_too_old, gateway_unreachable,
unavailable, blocked_by_policy, or blocked_by_user. Desktop starts no local connector while
the effective isLocalDevMcpEnabled is false, or while the user's own
features.isLocalDevMcpEnabled is false. On a bootstrap seat the value the gateway serves
decides, not the MDM profile's.
| State | Text |
|---|---|
blocked_by_policy | blocked: your organization's Claude Desktop policy turns off local connectors (isLocalDevMcpEnabled). |
blocked_by_user | blocked: you turned off local connectors in Claude Desktop's settings (features.isLocalDevMcpEnabled). |
Static profiles
A static profile (anyray-connect desktop fleet static) that an older Connect generated carries no
anyray row. On such a seat, routed through the gateway the seat is enrolled to, Connect
registers its own anyray retrieval server in claude_desktop_config.json, the same entry as on an
unmanaged seat, and removes anyray-share, since that server shares too. share then reads
not_needed and sharedPages reads on. A bootstrap seat never gets it: the gateway decides there.
Connect removes that entry again when the seat leaves this lane: the profile gains an anyray row,
turns local connectors off, routes elsewhere, or becomes a bootstrap profile. It removes only an
entry it registered this way. An anyray entry already there, such as one from before the seat
was managed, stays as it is. A policy Connect cannot read changes nothing.
Where local connectors are off, the entry could not start, so Connect writes none:
| Who turned them off | mcp.remedy | sharedPages | What to do |
|---|---|---|---|
The profile (isLocalDevMcpEnabled) | static-profile-row | static_row_missing | Add the anyray row to the static profile: regenerate it and redeploy it (static configuration). |
The user (features.isLocalDevMcpEnabled) | user-local-connectors-off | blocked_by_user | Turn local connectors back on in Claude Desktop's settings, then run anyray-connect again. |
Both report mcp.reason not-delivered.
Artifacts page sync
On a managed seat, Connect copies shared pages into Desktop's
Artifacts page when the
effective policy routes Desktop through the gateway the seat is enrolled to. It files them under the
organization Desktop uses: the deploymentOrganizationUuid the bootstrap serves, else the MDM
profile's, else Desktop's placeholder organization. If that organization or the Desktop account
changes, Connect removes its pages from the folder it wrote before and writes only where Desktop
reads now. Signing out or offboarding removes them from every folder Connect wrote, managed or not.
status --json reports the last run as claudeDesktopArtifactsPage: a status (synced,
unchanged, deferred, skipped or incomplete) and, when it has one, a reason. The fleet sees
the same pair as the artifacts_page_sync check on the claude-desktop row, which never counts as
failing. The reasons a managed seat adds:
reason | Meaning |
|---|---|
managed_policy_unknown | Connect could not read the managed policy, whether it routes through your gateway, or its organization. Nothing is written until it can. |
gateway_mismatch | The managed policy routes Desktop to another gateway. |
desktop_running | Desktop was open. The copy waits for the next run with Desktop closed. |
purge_failed | Connect could not remove its pages from a folder it wrote before. It keeps a note of that folder and retries on the next run. |
Update path variants
Updating an npm install
npx anyray-connect@latest bootstraps the same managed standalone install as connect.sh;
it does not create a second steady-state npm installation. The first enrollment, apply,
repair, or fleet sync that may persist a runtime downloads and verifies the managed binary,
then registers every integration against ~/.anyray/bin/anyray-connect. Later npx runs
leave that launcher in charge.
Existing seats whose hooks or services point at an older version-pinned npm runtime migrate
on their next automatic refresh. Connect smoke-tests the new binary before repointing
registrations and keeps the currently running npm copy as rollback until the migration
succeeds. Run anyray-connect update to migrate immediately, or anyray-connect update --check to preview it.
Inside a devcontainer or Codespace this migration, and self-update generally, is skipped: the image updates by rebuilding, not by downloading a new binary. See Remote and ephemeral machines.
Platforms without a standalone binary
If Anyray does not publish a standalone asset for the current OS and architecture, Connect
keeps the version-pinned durable npm runtime instead. --version, update, and doctor
report that self-update is unavailable; use npx anyray-connect@latest to pick up new
releases on that platform. A copy placed by your IT team is never replaced. If your Claude Code
managed settings name it, the seat
moves to a self-updating copy.
When the updater is not moving a seat
Connect remembers what its last update attempts came to and reports it as the self_update check,
tool connect. A seat whose updater is working sends nothing, so a healthy fleet adds no rows.
The check is informational: it never puts a working seat in Needs attention.
| Status | Meaning | Who acts |
|---|---|---|
failed | Two or more update attempts in a row failed. The error class names the step: download_blocked, checksum_rejected, asset_too_large, activate_failed, smoke_failed, repoint_failed, launch_blocked or install_error, and on a desktop app seat also app_signature_rejected, app_archive_unreadable or app_engine_unreachable. launch_blocked means the self-updating copy would not start, so the seat stays on its MDM binary; it is reported after one attempt, and a refused copy is not retried until the next release. | Network team for a blocked, rewritten or substituted download, the user for a local install failure, endpoint security for launch_blocked (allow ~/.anyray/versions) |
unavailable | Two or more checks in a row could not read which version is published. | Network team: allow the binary to reach github.com |
opted_out | The developer ran anyray-connect update --auto off. | Only the developer; an admin can still send an update |
platform_unsupported | No standalone binary is published for this OS and architecture. | Use npx anyray-connect@latest there |
unreadable | Connect could not read its own record of recent attempts, so it cannot say. | Usually nobody; the next refresh rebuilds it. If it persists, ~/.anyray/refresh-state.json is damaged, or desktop-update.json on a desktop app seat |
A single failure is not reported. An unreadable report never clears an earlier failure. The row
clears on the next report after the updater succeeds, after the developer turns updates back on,
or once the seat moves to administrator-directed updates. The report carries only these fixed tokens, never a version, a URL or an error message.
Desktop app updates
The desktop app checks for a new version about every 6 hours of wall-clock time, so a Mac that slept still checks when it wakes. After a failed attempt it retries in 5 minutes, then 15, then an hour, then every 6 hours again. A failed install waits the full 6 hours, since the app already told the user.
Each attempt is written to ~/.anyray/desktop-update.json. On a desktop app seat, doctor prints
it under Desktop app updates, and doctor --json carries it as selfUpdate.desktopApp, with
version (the release the attempt found or tried) and at (when):
state | Meaning |
|---|---|
never_ran | The app has not finished a check since it started keeping the record. |
up_to_date | The published version is not newer than this one. |
staged | A verified update waits for Update now in the tray menu. |
installing | Handed to Apple's Installer or the Windows installer, which has not finished. |
installed | Installed in place; the app restarted into it. |
manual | A newer version is published, with no package for this platform. |
failed | The attempt stopped at failureClass: download, manifest, hash, signature, archive, install or engine. failures counts failed attempts since the last completed one; a staged update or an installer handoff does not reset it. Doctor marks two or more as failing and names the fix. |
unreadable | The file exists but is not a record. The app rewrites it on its next check. |
On these seats the self_update check reports the app's updater, with the same thresholds:
download as download_blocked, manifest as unavailable, hash as checksum_rejected,
install as install_error, signature as app_signature_rejected, archive as
app_archive_unreadable and engine as app_engine_unreachable. Older Connect versions report
the last three as install_error and send no desktopApp field.
doctor --json and status --json include profile.state: present, absent (never connected, so default hook settings apply), or unreadable (the profile file exists but cannot be read or parsed). While unreadable, every Claude Code hook the team can switch off runs off, and doctor counts it as a problem. Run anyray-connect again to rebuild the profile; the team's hook settings return on the next policy pull.
doctor and status --json include sourceTrim.state: unsupported, needs_gateway_upgrade, observing, active, active_no_source_trim, off_by_policy, or unavailable. This is successful trim evidence, separate from the local hook invocation/probe check. status reads it from the verified key on GET /v1/me. Support and observation window.
With readable evidence, doctor adds sourceTrim.lastTrimAt (when the hook last filed a trim; absent if it never has) and sourceTrim.requestsSinceLastTrim (requests on the key from Claude Code or an SDK agent since then; each trim resets it to 0, so it never counts trims). The human report prints both on one line under the state.
doctor reads the gateway's record for its own key once, from GET /connect/self-check (the record the console's Health pill reads). doctor --json carries it as gatewayKey; the human report prints it under "Gateway's view of this key": the last request and tool, retrieval evidence, the last connect check-in, the connect version the gateway last heard, and the key's expiry. gatewayKey.state is reported, key_unavailable, needs_gateway_upgrade, rejected, rate_limited, unavailable, or unreachable. Only reported carries the record, and sourceTrim is derived from it. needs_gateway_upgrade, key_unavailable, rate_limited and unavailable keep the GET /v1/me read; unreachable and rejected skip it, since it would fail the same way. None of these adds to problems. gatewayKey is absent under --quick or with no key.
Gateway certificate
doctor checks whether this machine trusts the gateway's TLS certificate: first with Node's
built-in roots, then with the OS trust store added. It completes a TLS handshake and sends nothing
over it. When https_proxy or HTTPS_PROXY routes the gateway (and NO_PROXY does not match it),
the check goes through that proxy, as Connect's own requests do. A proxy it cannot use reads
unreachable, never a direct connection.
The result is one line under Gateway, and gatewayTrust in doctor --json and status --json:
state, plus rootSubject (the root's common name) for os_store, or code (the TLS error code)
for untrusted and invalid_cert. It never carries a certificate. untrusted and invalid_cert
each count as one problem. A report from an older Connect has no gatewayTrust.
| State | doctor prints | Meaning |
|---|---|---|
public | Certificate: publicly trusted. | Node's built-in roots verify it. |
os_store | Certificate: trusted by this machine's certificate store (company root: <name>). | Only the OS trust store verifies it. |
untrusted | This machine does not trust your gateway's certificate. Install your company's root CA, then run anyray-connect again. | Neither verifies it. Add the company root to the OS trust store. |
invalid_cert | Certificate problem: <code>. Check the gateway's certificate and this machine's clock. | Expired, not yet valid, or issued for another hostname. No trust store fixes it. |
not_tls | Certificate: none, the gateway URL is http://. | The gateway URL is http://. |
unreachable | No line; the reachability check reports it. | No TLS answer: DNS, a refused connection, a timeout, or plain HTTP on an https:// URL. |
not_checked | No line. | The check did not run: doctor --quick, the doctor run inside repair, or status --json with no gateway set. |
Claude Code on an older Node, with os_store
With os_store, Claude Code needs one more thing only when it is an npm install run by a Node that
cannot read the OS trust store (older than 22.15, or 23.0 to 23.9). Connect then writes the roots
that completed the chain to ~/.anyray/ca/gateway-roots.pem, each root once, and sets
env.NODE_EXTRA_CA_CERTS in ~/.claude/settings.json to that path.
- A value you or your admin already set (user or managed settings, or a shell export) is kept. Connect remembers a shell export so its background refresh keeps it too.
- Connect removes its own value and file once Claude Code can read the OS store, or when the state
becomes
public,untrustedornot_tls.unreachableandinvalid_certleave them as they are, and so does a Claude Code whose Node version Connect cannot read. - Running as root, Connect leaves all of this alone. Reverting Claude Code removes them too.
doctor --json reports this as claudeCodeCaFallback.value: connect (Connect's file is in use),
own (someone else's value, kept), or absent (not set yet; the next anyray-connect run sets
it), and runtime as old_node, unknown or skipped_as_root. None of these adds to problems.
Desktop uninstall evidence
Both doctor --json and status --json include:
{
"uninstall": {
"available": true,
"lastResult": {
"user": "complete",
"residue": "needs_admin",
"loginItem": "unregistered",
"at": "2026-09-10T10:00:00.000Z"
}
}
}
lastResult is absent until uninstall has run. residue: "needs_admin" means the user-owned
configuration is already removed but the OS package remains for an administrator. On macOS that
includes a Mac where a directory above the uninstaller is not owned by root alone (Homebrew on Intel
takes over /usr/local/lib): Connect will not hand Installer a package another process could have
swapped, and an app moved out of /Applications stays for an administrator too. loginItem: "stale_entry" means the --user (MDM/fleet-script) invocation could not reach a login session to
unregister the item — nobody is signed in to answer it; removing the package leaves that fixed
entry harmless. "unregistered" also covers a machine where no app or package was ever adopted, so
there was nothing to unregister. The receipt contains only these closed states and a timestamp.
Batch reads
Linux, macOS and Windows MCP hosts get complete batch reads by default, subject to separate MCP tool approval.