Providers
Can each coding subscription's quota be spent headlessly, on a machine other than the developer's? Researched from vendor docs, ToS and changelogs. Tags: [V] vendor docs, [C] community or press, [I] inference. Anything [C] or [I] that the design depends on needs a burn test, recorded under Real runs.
Summary
| Provider | Headless credential | Remote | Bills | Quota model | Quota readable | Automation ToS |
|---|---|---|---|---|---|---|
| Claude Code | claude setup-token, 1 year, static | Yes, documented for CI [V] | Subscription [V, burn-tested] | 5 h + weekly windows | rate_limit_event in stream-json [V, tested] | Stock binary only [V] |
| Codex (ChatGPT plan) | ~/.codex/auth.json, rotates at every refresh [V, tested] | Yes, "advanced" [V] | Subscription | 5 h + weekly windows; free plan one 30-day window [V, tested] | App-server account/rateLimits/read [V, tested] | Own login, no sharing [I] |
| GitHub Copilot CLI | Fine-grained PAT or Actions GITHUB_TOKEN | Yes, documented [V] | AI Credits, monthly allowance [V] | Monthly credits | Billing and usage APIs [V] | Low [I] |
| Cursor CLI | CURSOR_API_KEY, static [V, tested] | Yes, documented [V] | Plan pool [V, tested] | Monthly pool (legacy), weekly pool (X Subscription) [V] | Dashboard only [V, tested] | Low [I] |
| Gemini CLI (AI Pro/Ultra) | Cached OAuth ~/.gemini/oauth_creds.json | Works, not sanctioned [C] | Subscription only via OAuth [V] | Daily requests | /stats, interactive only [V] | High [V] |
| Gemini Code Assist | Workload Identity / Vertex | Yes, run-gemini-cli [V] | License [V] | Daily requests | No [I] | Low [I] |
| Jules | API key (alpha) | Yes [V] | Plan tasks [I] | Daily tasks | No [I] | Low [I] |
| Kiro (ex Amazon Q CLI) | KIRO_API_KEY, Pro plans | Yes, headless mode [V] | Plan credits [I] | Monthly credits | ? | None found |
| Factory Droid | FACTORY_API_KEY, droid exec | Yes, droid-action [V] | Plan pool [I] | 5 h / 7 d / 30 d | ? | None found |
| Amp | AMP_API_KEY, amp -x | Yes [V] | Credits [V] | Credits | ? | None found |
| Warp (Oz) | WARP_API_KEY | Yes, oz-agent-action [V] | Credits [V] | Monthly credits | ? | None found |
| Augment (Auggie) | AUGMENT_SESSION_AUTH | Yes [V] | Plan credits [V] | Monthly credits | --show-credits [V] | One user per token [V] |
| Devin (ex Windsurf) | DEVIN_API_TOKEN, devin -p | Yes [V] | ACUs; personal plans unclear | Credits | ? | None found |
| Mistral Vibe | MISTRAL_API_KEY | Yes [V] | Shared monthly allowance [V] | Monthly | ? | None found |
| JetBrains Junie | Subscription via OAuth only | Local only [I] | API key is usage-billed [V] | — | ? | — |
| Z.ai GLM Coding Plan | Plan API key | Technically yes | Subscription [V] | Rolling + weekly | ? | Official tools only, no raw API [V] |
| Kimi Code | Plan API key | Technically yes | Membership [V] | Rate-limited | ? | No User-Agent changes [V] |
| Alibaba Coding Plan (Qwen) | sk-sp- key | Forbidden | Subscription [V] | 5 h / week / month | ? | No non-interactive use [V] |
| MiniMax Token Plan | sk-cp- key | Technically yes | Subscription [V] | Plan | ? | Full ToS not read |
| OpenCode Go | OPENCODE_API_KEY | Technically yes | $ caps per 5 h / week / month [C] | Rolling | ? | Not found |
Z.ai, Kimi, MiniMax and Alibaba expose Anthropic-compatible endpoints, so the stock claude binary runs on them with ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN [V].
Groups
- Static token, remote-ready. Claude Code, Copilot, Cursor, Kiro, Factory, Amp, Warp, Augment, Devin, Mistral. Create once, store as a secret.
- Rotating login file, remote with care. Codex, and Gemini on a personal plan. One file per machine, serialized runs, write the refreshed file back after every run.
- ToS-limited. Alibaba forbids unattended runs; Z.ai and Kimi allow only official tools with their real identity.
- Local only. Junie's subscription.
What matters for awaywork
- Idle quota only expires on rolling windows. Claude, Codex, Factory, Z.ai, Alibaba, OpenCode Go (hours to a week) and Gemini, Jules (daily) lose what a night leaves unused. Cursor's X Subscription plans lose it weekly. Monthly credit plans (Copilot, Cursor's legacy plans, Amp, Warp, Augment, Mistral) lose it only at month end; scheduling them nightly is about capacity, not waste.
- Always the vendor's unmodified binary with the user's own credential. Never call the backing API with a subscription token, never store, pool or proxy other users' credentials, never spoof the client identity. Anthropic (enforced since April 2026), Google (bans in February 2026) and Z.ai punish exactly that.
- Vendors ship their own schedulers: Claude routines, Codex scheduled tasks, Cursor Automations, Copilot coding agent via Agentic Workflows, Jules scheduled tasks, Warp, Devin Cloud, Factory Missions, Amp Orbs. None paces work against remaining quota across providers.
Per provider
Claude Code
claude setup-tokenprints a one-year OAuth token; setCLAUDE_CODE_OAUTH_TOKEN. Inference only: no Remote Control, no claude.ai connectors.ANTHROPIC_API_KEYorapiKeyHelperoutrank it;--bareignores it. [V] https://code.claude.com/docs/en/authenticationclaude-code-actiontakesclaude_code_oauth_token; docs show cron and say runs use the subscription. Do not share one token across an org. [V] https://code.claude.com/docs/en/github-actions- Copying
~/.claude/.credentials.jsoninstead gives a refreshing login that eventually needs/login. [V] - ToS: OAuth is for "ordinary use of Claude Code and other native Anthropic applications"; third parties may not route requests through Pro/Max credentials or "collect, store, or intermediate" them; signing in to the unmodified binary with your own subscription is allowed. Limits "assume ordinary, individual usage". [V] https://code.claude.com/docs/en/legal-and-compliance
- Agent SDK with subscription OAuth is not permitted; enforcement against third-party harnesses from 4 April 2026. [C] https://www.theregister.com/software/2026/02/20/anthropic-clarifies-ban-on-third-party-tool-access-to-claude/5014546
- Anthropic announced moving
claude -p, the Agent SDK and GitHub Actions to a separate credit billed at API prices, then paused the change and promised notice. If it returns, headless runs stop drawing on the subscription. [C] - Quota: status-line JSON
rate_limits.five_hour/seven_day[V] https://code.claude.com/docs/en/statusline;rate_limit_eventinclaude -p --output-format stream-json --verbose(tested, Real runs). --max-turnsends the run itself: with 2, the stream stops after two model messages with aresultof subtypeerror_max_turnsand exit 1 (2.1.282).num_turnsin the result counts more than model messages (3 there). [V, tested]- Skills: a
SKILL.mdfolder under.claude/skills/in the working directory is listed in theinitevent'sskillsand callable by a headless run that allows theSkilltool (2.1.282). [V, tested] - What
claude -ploads, checked offline with a fakeHOMEand a stub API (2.1.284). With no flag: user and projectCLAUDE.md, user and project skills,SessionStarthooks from~/.claude/settings.json,.claude/settings.jsonand.claude/settings.local.json, MCP servers from~/.claude.jsonand an unapproved.mcp.json(both started), and auto-memory under~/.claude/projects/.--setting-sources project,localdrops every user-scope item but auto-memory;--setting-sources ''drops the project ones too except.mcp.json, which only--strict-mcp-configdrops;--settings '{"autoMemoryEnabled":false}'drops auto-memory.--add-dirloads no skills under--setting-sources ''. [V, tested] User plugins are enabled from user settings, so they go with them. [I] Workers runproject,localon the developer's own branches and''with--strict-mcp-configon someone else's PR and in the probe, all without auto-memory. - Under
--setting-sources '', checked the same way (2.1.284): a--plugin-dirfolder with.claude-plugin/plugin.jsonnamednightshiftandskills/<name>/SKILL.mdlistsnightshift:<name>in theinitevent'sskills, while the clone's.claude/skills/stays out; aSessionStarthook in the--settingsJSON runs. [V, tested] Namedawayworksince the rename. [I] So workers get their skills as that plugin, outside the clone, and the developer'ssettingsfile merged into the--settingsJSON. - Headless runs refuse writes under
.claude/without a grant, even withWriteallowed (permission_denied,safetyCheck). [V, tested] - An empty
GH_CONFIG_DIRhides a keyringghlogin fromgh auth statusbut not fromgh auth tokenorgh auth git-credential get(2.102.0);glabwith an emptyGLAB_CONFIG_DIRthe same,twgnot. [V, tested] With that empty dir,GH_TOKEN=nonemasks the keyring login:gh auth tokenprintsnone,gh auth git-credential getreturnspassword=none(2.102.0). [V, tested]GH_ENTERPRISE_TOKEN=nonedoes the same for an Enterprise host (gh auth token -h github.acme.comprintsnone), checked without a keyring login on such a host. [C] - Sandbox, checked on macOS with a stub API (2.1.284):
claude -p --settings '{"sandbox":{"enabled":true}}'runs Bash with network denied (deny network-outboundin the tool result) and writes limited to the working directory;git committhere works. A Bash call withdangerouslyDisableSandboxis refused headless, and withallowUnsandboxedCommands: falseruns sandboxed anyway. Sandboxed Bash can't write.claude/settings.jsonor.claude/settings.local.json. Project settings merge in: theirsandbox.enabled: falseloses to the flag, but theirsandbox.network.allowedDomainsor aWebFetch(domain:…)allow rule opens those domains. [V, tested] The binary says a missing sandbox backend runs the shell unconfined unlesssandbox.failIfUnavailableis true [V, binary strings]; the Linux backend (bubblewrap) is not tested. - The sandbox covers Bash, PowerShell and Monitor only; Read, Edit and Write are held by permission rules. Hooks and MCP servers run outside it. On Linux it needs bubblewrap and socat, and Ubuntu 24.04+ AppArmor stops bubblewrap creating user namespaces without a profile for it. [V] https://code.claude.com/docs/en/sandboxing Whether GitHub's
ubuntu-latestrunner has them is not checked, so offline there may fail to start [I]. - MCP servers, checked on macOS with a stub API, a stdio echo server and a streamable-HTTP one (2.1.289):
--mcp-configtakes the{"mcpServers":…}JSON as a string. Its servers load under--setting-sources project,local, and under--setting-sources '' --strict-mcp-config, which still drops the clone's.mcp.json.${VAR}expands from the binary's env inenvand inheaders(Bearer ${VAR}reached the server). A stdio server inherits the binary's whole env, so a secret meant for one server reaches every server and the worker's Bash.--allowedTools mcp__<server>allows all of a server's tools;mcp__<server>__<tool>allows just that one. A tool outside the list is denied. In three of the first runspermissionModereadautoin theinitevent, where a classifier rules on unlisted tools, anddefaultin every later one, with identical flags.--permission-mode dontAskdenies unlisted tools in every run and still runs the listed ones, so workers pin it. With the offline sandbox, the stdio server fetched a local port and the HTTP server was reached: neither is sandboxed. [V, tested] - Routines: cloud, cron (1 h minimum), API
/fire, GitHub events; plan usage plus a daily run cap. [V] https://code.claude.com/docs/en/routines
Codex
codex login --device-auth, copy~/.codex/auth.json, or forward port 1455. [V] https://developers.openai.com/codex/auth- CI with account auth is "advanced"; API keys recommended. Tokens refresh about every 8 days and rewrite
auth.json; "Do not share the same file across concurrent jobs or multiple machines". Persist the refreshed file; seed only when missing. [V] https://developers.openai.com/codex/auth/ci-cd-auth openai/codex-actiontakes API keys only. [V] https://github.com/openai/codex-action- Quota:
codex app-server(stdio JSON-RPC:initialize,initialized, thenaccount/rateLimits/read) returnsrateLimits.primaryand.secondary, eachusedPercent(0–100),windowDurationMins,resetsAt(unix s), plusplanType. The server does not exit on its own. [V, tested 0.149.1] The request names its client: the user agent readsnight-shift/<codex version>. [V, tested] Nowawaywork/<codex version>. [I] - On the free plan the only window is
primary, 43200 min (30 days),secondarynull. Before any useresetsAtwas now + 30 days; the window started at the first run. [V, tested] Plus/Pro show a 5 h primary and a weekly secondary. [C] - Free-plan cost: one trivial
exec(two shell calls, one edit) moved the month 0% → 2%; a smallimplementunit (a typo, a commit, the PR body) 4% → 9%; ten tool calls of areviewthat read a 1,000-line diff 9% → 20%. So the paced ceiling (~3% a run on a 30-day window, with one run a day) is below one unit. [V, tested] - The access token lasts 10 days (
exp - iatof the JWT) andauth.jsonsays when it was last refreshed. [V, tested] - The first probe refreshed a 12-day-old
auth.jsonin place (last_refreshmoved, login still valid). So probes rotate the file too and share the per-machine lock with runs. [V, tested] - A refresh fires when the access token expires, not when
last_refreshis 8 days old as the CI guide says, and every refresh rotates the refresh token. [V, tested 0.162.1] - A replaced refresh token still refreshed 8 s, ~6 min and ~30 min later, and the live login kept working beside it. After ~27 h it failed with "refresh token was already used", and the live login still refreshed. So a secret seeded on an ephemeral runner fails the night after its first refresh. [V, tested 0.162.1]
- Several
codex login --device-authon one account coexist: a new login revokes none, andcodex logoutof one leaves the others working. On macOS the default credential store writesauth.json. [V, tested 0.162.1] codex exec --jsonprints JSONL:thread.started,turn.started,item.started/item.completed(agent_message,command_executionwithexit_code,file_change,error), thenturn.completedwith token usage, orerrorandturn.failedwith exit 1. No quota and no turn count in the stream. [V, tested 0.149.1]exechas no turn cap flag [V,codex exec --help]; awaywork counts tool calls (commands, edits, MCP calls, web searches) as turns and kills the binary past the cap. The quota is read after the run, onceexechas exited: an app server started beside a runningexecwould shareauth.jsonand could race a token refresh [I]. So a Codex worker stops at its ceiling only between units, never mid-run.- The
workspace-writesandbox refuses.git/index.lock, so the model cannot commit; workers run--sandbox danger-full-access, like Claude's unsandboxed Bash. [V, tested] - Network off with
.gitwritable (0.149.1, macOS,codex sandboxandcodex execon a stub Responses API):workspace-writeblocks the network, andsandbox_workspace_write.writable_rootsdoesn't reopen.git. A permission profile passed with-cdoes:default_permissions="night",permissions.night.filesystemwith":root"="read", the clone and<clone>/.gitas"write", andpermissions.night.network.enabled=falsecommits, blockscurland blocks writes outside the clone. Without the explicit.gitentry.gitstays read-only.--sandboxoverrides the profile, so it must be left out.-c web_search="disabled"drops the hostedweb_searchtool from the request. [V, tested] Linux (Landlock) not tested. -c sandbox_mode="danger-full-access", before or after the profile, doesn't reopen the network or writes outside the clone; alone it does. Neither does an earlierpermissions.night.network.enabled=true, and aprofilekey is refused (0.149.1, macOS,codex execon a stub). [V, tested]--ignore-user-configskipsconfig.toml(MCP servers, notify hooks, plugins) while auth still comes fromCODEX_HOME[V,--help; tested];--ephemeralsaves no session. A prompt argument plus piped stdin appends stdin, so the prompt goes in on stdin as-. [V,--help]- MCP servers (0.160.0, macOS,
codex execon a stub Responses API, isolatedCODEX_HOME,env -i):-c mcp_servers.<name>.command|args|env|env_vars|url|bearer_token_env_var|http_headers|env_http_headers|enabled_toolsregisters a server under--ignore-user-config, and TOML arrays and inline tables work through-c. A server gets only the variablesenv_varsforwards, plusHOMEandPATH.shell_environment_policy.excludehides those variables from the model's shell while the server still gets them.enabled_toolsdrops the other tools from the request; a forced call to one is refused.execnever prompts: a call that needs approval fails at once with "MCP tool call requires approval, but approval policy is never". Under--sandbox danger-full-accessevery tool runs. Under the offline permission profile only tools annotatedreadOnlyHintrun, unless the server setsdefault_tools_approval_mode="approve".- Under the offline profile the stdio server still fetched the network and wrote outside the clone, and the HTTP server was reached.
- A server that fails to start or times out (default about 10 s) is dropped silently, with exit 0, so workers set
startup_timeout_secto 60. - An
item.completedof typemcp_tool_callcarriesserver,tool,arguments,result,errorandstatus. [V, tested]
- Skills:
.agents/skillsin every directory from the working directory up to the repo root [V] https://learn.chatgpt.com/docs/build-skills; aSKILL.mdthere was found and followed by a headlessexec. [V, tested] Whetherexecreads skills from anywhere outside the clone under--ignore-user-configis unchecked [I], so workers still get theirs copied into the clone.-c key=valueoverrides a config value, parsed as TOML [V,codex exec --help]; that it applies under--ignore-user-configis [I]. Instructions:AGENTS.md, root down to the working directory [V] https://learn.chatgpt.com/docs/agent-configuration/agents-md - Scheduled tasks run in the cloud from web or mobile; desktop tasks on local projects need the app running. [V] https://learn.chatgpt.com/docs/automations
GitHub Copilot
- CLI reads
COPILOT_GITHUB_TOKEN,GH_TOKEN,GITHUB_TOKEN; fine-grained user PAT with "Copilot Requests"; classic PATs fail. [V] https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/authenticate-copilot-cli - In Actions,
permissions: copilot-requests: writereplaces the PAT; in an org repo it bills the org. [V] https://github.blog/changelog/2026-07-02-copilot-cli-no-longer-needs-a-personal-access-token-in-github-actions/ - Premium requests became token-metered AI Credits in June 2026. [V] https://github.blog/news-insights/company-news/github-copilot-is-moving-to-usage-based-billing/
- Quota: billing usage REST API and per-user
ai_credits_used. [V] https://docs.github.com/en/billing/tutorials/automate-usage-reporting - Coding agent can be assigned an issue via API, so cron can drive it; Agentic Workflows (gh-aw) support schedules. [V] https://github.github.com/gh-aw/reference/auth/
Cursor
CURSOR_API_KEYor--api-key; recommended for CI. [V] https://cursor.com/docs/cli/reference/authentication, https://cursor.com/docs/cli/github-actions- Plans: legacy Pro/Pro+/Ultra have monthly pools; X Subscription plans (Plus, Premium, Super, Ultra) one weekly pool shared with Grok. Unused usage doesn't roll over. Remaining usage shows in the dashboard and the editor only. [V] https://cursor.com/help/models-and-usage/usage-limits
- Billing: on a legacy Pro plan, nine
composer-2.5runs with a user API key and one with the login showed up as plan usage in the dashboard. Which request drew from which source was not checked. [V, tested] - Checked on macOS with
agent2026.10.22-27dcf5f, a Pro plan,composer-2.5; [V, tested] unless marked:- The binary installs with
curl https://cursor.com/install | bashinto~/.local/share/cursor-agent/versions/<v>/, linked asagentandcursor-agent; no npm package.cursor-agent updateon 2025.09.17 exited 1 without a message.about --format jsonprintssubscriptionTier;modelslists model ids. - The login is two macOS Keychain items,
cursor-access-tokenandcursor-refresh-token, so a differentHOMEhides it.CURSOR_CONFIG_DIRkeeps the login but only movescli-config.json: rules, skills, hooks andmcp.jsonthere are ignored. - With
CURSOR_API_KEYand a freshHOMEa run works (apiKeySource: envin theinitevent) whilestatussays not logged in. The binary then tries to save its session to the Keychain, which fails there with a warning and raises a macOS alert.AGENT_CLI_CREDENTIAL_STORE=memory(file,memoryordefault) skips the save and the alert. [V, binary strings; tested] Elsewhere the default store is$XDG_CONFIG_HOME/cursor/auth.json. [I, binary strings] - A bad key or model exits 1 with the reason on stderr and nothing on stdout. A prompt on stdin works with
-p. --output-format stream-jsonprintssystem/init,user,thinkingdeltas (the docs say they are suppressed),tool_callstarted/completedpairs keyed bycall_id,assistant, then oneresultwithsubtype,is_error,duration_msandusagetoken counts. The tool is the one*ToolCallkey:shellToolCall(result.success.exitCode, orrejected),readToolCall,editToolCall,grepToolCall,globToolCall,mcpToolCall(args.providerIdentifier,args.toolName;result.success,rejectedorpermissionDenied) andgetMcpToolsToolCallfor tool discovery. Calls run in parallel. No quota or rate limit anywhere.- No turn cap flag. Killing the process group after three completed calls exits 143 with no
resultand leaves nothing running. Every run starts aworker-serverchild in the same process group that outlives the agent for minutes, with its environment,CURSOR_API_KEYincluded. - With the developer's
HOMEa run loads every user skill (~/.agents/skills,~/.claude/skills, Claude Code plugins' skills), the user MCP servers and user rules. With a freshHOMEit gets Cursor's built-in skills (synced into~/.cursor/skills-cursor) plus the clone's.cursor/skills,.claude/skills,.agents/skills,AGENTS.md,CLAUDE.mdand.cursor/rules. --plugin-dirwith.cursor-plugin/plugin.jsonor.claude-plugin/plugin.jsonandskills/<name>/SKILL.mdadds those skills.- Hooks in
~/.cursor/hooks.jsonand in the clone's.cursor/hooks.jsonrun in print mode,--trustor not:sessionStart,preToolUse,beforeShellExecution,afterFileEdit;beforeSubmitPromptandstopdidn't fire. No switch to drop the clone's was found. Hooks in the clone's.claude/settings.json(SessionStart,PreToolUse) and.claude/settings.local.json(PreToolUse) run too, with a freshHOMEand in print mode. [V, tested] The binary also reads permissions from<git root>/.claude/settings.json. [V, binary strings] - A subagent shows in the stream as one
taskToolCall(args.description,args.prompt;result.success.conversationStepsholds only its final text): the commands it ran never appear astool_callevents. No permission token to deny subagents was found in the binary's strings. [V, tested] - MCP: servers in
~/.cursor/mcp.jsonstart without a flag; the clone's.cursor/mcp.jsononly with--approve-mcps. A stdio server getsHOME,PATHand its ownenvonly, where${env:NAME}expands from the binary's environment.enabledToolsis accepted but not enforced: an unlisted tool was listed and called. - Permissions live in
~/.cursor/cli-config.jsonand the clone's.cursor/cli.json, whoseallowlists add up. Without--force, print mode runs whatallowlists and returnsrejectedfor the rest, soallowis an allowlist (Shell(*),Read(**),Write(**),Mcp(server:tool),Mcp(server:*)).denywins, also under--force, which otherwise runs everything. An invalid.cursor/cli.json, asandboxkey included, fails the run with exit 1. - Sandbox:
--sandbox enabledwithout--forceruns a shell command underTYPE_WORKSPACE_READWRITEwith the network off (CONNECT tunnel failed, response 403) and writes outside the clone refused; the edit tool andgit commitwork. A command matching anallowrule runs unsandboxed (TYPE_INSECURE_NONE) or with the network on, and--forceturns the sandbox off. - A commit made through the shell got no trailer, though
cli-config.jsondefaultsattribution.attributeCommitsToAgentto true. The agent's commit on the hosted run below gotCo-authored-by: Cursor <cursoragent@cursor.com>[V, tested]; whether the default or the agent's own message added it wasn't isolated.
- The binary installs with
- Install outside the runner's home, checked in an
ubuntu:24.04amd64 container (qemu, not a GitHub runner):sudo env HOME=/opt/cursor bash -c 'curl https://cursor.com/install -fsS | bash'puts everything under/opt/cursor/.local(558 MB, mode 755, nothing in/root). Linked into/usr/local/bin, it runs as a no-homeawayworkuser with its ownHOME,CURSOR_API_KEYandAGENT_CLI_CREDENTIAL_STORE=memory: acli-config.jsonShell(*)allow ran the shell call and the run endedresult/success, exit 0. It wrote only under itsHOME(.cache,.cursor), nothing under/opt/cursor, and stderr was empty. [V, tested] - Install as the runner's user, not root, as the action's
scriptsstep does on a GitHub-hostedubuntu-latestrunner:sudo install -d -o "$USER" /opt/cursor, thencurl -fsS https://cursor.com/install | HOME=/opt/cursor bashandsudo ln -sf "$(readlink -f /opt/cursor/.local/bin/agent)" /usr/local/bin/agent. On the runner, with the pipe still insideHOME=/opt/cursor bash -c '…', the installer detectedlinux/x64and fetchedagent2026.10.22-27dcf5f, and the step took 4.1 s, a finalagent --versionincluded. Checked in the container only:/opt/cursoris 755 and the runner's, the version folder 775runner:runner, the installer writes nothing to the runner's home (the finalagent --versionwrites~/.cache/cursor-compile-cacheand~/.cursor/cli-config.jsonthere), acurlthat can't connect fails the step under the action'sbash -eo pipefail -c(exit 7) where inside an innerbash -cit didn't (exit 0), and theawayworkuser runsagent --versionand a headless run but can't write/opt/cursor. [V, tested] - Instructions:
AGENTS.mdandCLAUDE.mdat the root, plus.cursor/rules. [V] https://cursor.com/docs/cli/using Skills:.agents/skills,.cursor/skills,.claude/skills,.codex/skillsand their~twins. [V] https://cursor.com/docs/skills - ToS: no clause on unattended or headless use; no renting, lending or selling, and the account holder answers for all activity. [V] https://cursor.com/terms-of-service
- Automations: cron or events, billed as cloud-agent usage. [V] https://cursor.com/docs/cloud-agent/automations
Gemini
- Headless reuses cached OAuth; otherwise
GEMINI_API_KEYor Vertex. [V] https://geminicli.com/docs/get-started/authentication/ - AI Pro 1,500 and Ultra 2,000 requests/day via OAuth; an unpaid API key gets 250 and not the subscription. [V] https://geminicli.com/docs/resources/quota-and-pricing/
- ToS: using the services behind Gemini CLI from third-party software is a violation. [V] https://geminicli.com/docs/resources/tos-privacy/ Mass bans in February 2026. [C] https://github.com/google-gemini/gemini-cli/discussions/20632
run-gemini-clisupports Code Assist, Vertex, WIF, API key; not AI Pro OAuth. [V] https://github.com/google-github-actions/run-gemini-cli- Jules: API with keys, 15/100/300 tasks per day, scheduled tasks in the UI only. [V] https://developers.google.com/jules/api, https://jules.google/docs/usage-limits/
Others
- Kiro: https://kiro.dev/docs/cli/headless/ (Amazon Q CLI end of support 30 April 2027)
- Factory: https://docs.factory.ai/cli/droid-exec/overview, https://docs.factory.ai/pricing
- Amp: https://ampcode.com/manual, https://ampcode.com/docs/pricing
- Warp: https://docs.warp.dev/reference/cli/
- Augment: https://docs.augmentcode.com/cli/automation/overview
- Devin: https://docs.devin.ai/cli/reference/commands
- Mistral Vibe: https://docs.mistral.ai/vibe/code/cli/api-keys-profiles
- Junie: https://junie.jetbrains.com/docs/junie-cli.html
- Z.ai: https://docs.z.ai/devpack/usage-policy
- Kimi: https://www.kimi.com/code/docs/en/
- Alibaba: https://www.alibabacloud.com/help/en/model-studio/coding-plan
- MiniMax: https://platform.minimax.io/docs/token-plan/quickstart
- OpenCode Go: https://opencode.ai/docs/go/
Real runs
What real runs showed. The prototype was a private set of scheduled GitHub Actions running headless Claude Code on one team's leftovers.
Claude Code on a hosted runner
A
claude setup-tokentoken runs headless Claude Code on a GitHub runner, billed to the subscription: 9 Fable turns (22k output, 227k cached input, $1.76 list) moved the session window 9% → 13% and the weekly window 35% → 36% while the developer's machine was idle. The result'scostBasis: listsays nothing about billing. [V, prototype]A real fix unit (one PR, 18 bot threads, 7 commits, 49 tool calls, ~8 min on Opus) moved the session window 7% → 9%. [V, prototype]
Reading quota: [V, prototype]
Where claude -p /usageapi.anthropic.com/api/oauth/usagerate_limit_eventLogged-in machine Works Not tested Works Runner with setup-token Prints a cost summary instead 403, then 429 for 1 h Works One Haiku turn reads it anywhere:
shclaude -p "reply ok" --model claude-haiku-4-5-20251001 --max-turns 1 \ --output-format stream-json --verbose \ | jq -c 'select(.type == "rate_limit_event") | .rate_limit_info' | tail -1json{"status":"allowed","rateLimitType":"five_hour","unifiedWindows":{ "five_hour":{"utilization":0.02,"resetsAt":1790254200}, "seven_day":{"utilization":0.36,"resetsAt":1790391600}}}Identical to
/usageon the same machine at the same moment, with no per-model weekly window. A worker's own stream carries it after each response.OAuth-only MCP servers (claude.ai connectors) don't work with a setup-token; REST with a personal token, or an MCP server with a bearer key, does. [V, prototype]
Runners die quietly
Two fix units ended with "The hosted runner lost communication with the server" after 75–85 min, with no log and no artifact. A monitor and a diagnosis pass on the same runner found disk exhaustion (6 GB yarn cache, 2 GB node_modules) plus a whole-repo type check larger than the 7 GB runner, started as a background task. [V, prototype] What came out of it: the action frees about 20 GB of preinstalled SDKs (action.yml), a watchdog stops a unit below 1.5 GB of memory or 2 GB of disk, and the harness forbids whole-repo checks, builds and background commands.
- Claude Code's background Bash tasks start their own session, so a process-group kill misses them. [V, prototype] They stay children of
claudewhile it runs. [I] - Node can't become a child subreaper without a native addon, and a cgroup needs a delegated subtree or root, which a laptop run doesn't have. [I] So the watchdog polls
psevery 2 s and at the end kills each recorded descendant still alive with the same start time: a detached grandchild whose parent exited is killed [V, tested on macOS; the same test runs on Linux in CI]. One born and orphaned between two polls escapes. [I] - Memory is
process.availableMemory(): on macOS it matchedvm_statfree + inactive + speculative (45 GB against 44 GB), whereos.freemem()counted free pages only (24 GB). [V, tested] On Linux libuv readsMemAvailable, capped by the cgroup limit. [C] - Disk is
statfson the clone's filesystem. [V]
awaywork's burn tests
- One Claude unit (Claude Code 2.1.282, the developer's login,
implementon an issue of this repo, a 3% cap per run): 57 s, 6 turns, one commit and a PR body. The publisher pushed a new branch and opened a draft PR with the footer and the trailer, and the nextplanskipped the issue as waiting for review. Neither window moved at 1% resolution, so stopping at the ceiling wasn't exercised. A new issue took a few seconds to show up in GitHub search. [V, tested] - Two providers (
providers: [codex, claude], Codex 0.149.1 on a free ChatGPT plan, Claude Code 2.1.283, a 3% cap per run,reviewwithdeliver: reporton two PRs): Codex took the first unit, read its month at 20% against an 11.7% ceiling and was stopped after 1 min 30 s, keeping the report it had written. The second unit was re-routed to Claude, which finished in 28 turns (session 9% → 11%, week unmoved). The first probe refreshed a 12-day-oldauth.json, and the login stayed valid. That build read Codex's quota mid-run, outside the lock; it now reads it after the run. [V, tested] - A Claude worker stopped mid-run (Claude Code 2.1.308,
implementon throwaway issues, other sessions on the same account moving the session window about a point a minute). Withsession_max: 19%and the session at 17%, it stopped about 1 min 40 s in, on the first turn after the reading reached 19%:stopped: session, uncommitted work, nothing published. Withper_run: 1%(week at 31%, ceiling 32%), it stopped about 5 min in, on the first turn after the week reached 32%, killing the Bash command that turn had just started:stopped: quota, and its four commits went out as a draft PR. The stop fires when the reading reaches the limit, and Claude reports whole percents. awaywork logs only thestop:line, so the readings came from a Haiku probe polled each minute beside the run. [V, tested] - One Cursor unit through
test/real-cursor.test.ts(agent2026.10.22-27dcf5f on macOS, a legacy Pro plan,CURSOR_API_KEY, no model set,implementon a throwaway repo,isolation: none): about 28 s, one commit fixing the file and a PR body, a clean tree. ItsHOMEwas the fresh one awaywork made in.away/, holding thecli-config.jsonandmcp.jsonawaywork wrote plus Cursor's own state; the key appeared in no file there, and noworker-serverwas left running. [V, tested] - One Cursor unit on a GitHub-hosted
ubuntu-latestrunner (agent2026.10.22-27dcf5f installed by the action'sscriptsstep,providers: [cursor],CURSOR_API_KEYas a repository secret, no model set,implementon a sandbox issue asking for one README line, the worker user): about 26 s and 15 turns, one commit adding the line, a draft PR. Its commit message ended withCo-authored-by: Cursor <cursoragent@cursor.com>, then awaywork'sAwaywork:trailer. The whole job took about 3 min 20 s, 66 s of it freeing disk and 77 s making the worker user. [V, tested]
Unverified, burn-test before relying on it
- Cursor: whether an X Subscription key run draws the weekly pool.
- Kiro: API-key runs draw plan credits.
- Factory:
droid execshares the interactive pool. - Devin: personal-plan CLI billing.
- Codex: Plus/Pro window shape; whether an app server beside a running
execcan share oneauth.jsonsafely, which a mid-run quota read needs. Two refreshes minutes apart both succeed [V, tested], but two processes writing one file at once was not tested. - Offline workers on Linux: Claude's bubblewrap sandbox and Codex's Landlock profile were only tested on macOS.
- Codex remote: how an ephemeral runner saves the refreshed
auth.jsonback to its secret; the workflow'sGITHUB_TOKENcannot write secrets.