cd /news/ai-tools/show-hn-locum-grok-bot-delegates-tas… · home topics ai-tools article
[ARTICLE · art-107370] src=github.com ↗ pub= topic=ai-tools verified=true sentiment=· neutral

Show HN: Locum – Grok Bot delegates tasks to Claude/Codex CLI on your machine

A developer released Locum, an open-source server that lets xAI's Grok Bot delegate coding tasks to the Claude Code and Codex CLIs running on the user's own machine via a custom MCP connector and a Cloudflare tunnel, preserving the user's own subscription credentials. The project explicitly prohibits hosting for third parties and enforces invariants such as never reading credential files or calling vendor APIs directly, to comply with Anthropic's terms that disallow routing requests through Free, Pro, or Max plan credentials on behalf of others.

read11 min views1 publishedAug 22, 2026
Show HN: Locum – Grok Bot delegates tasks to Claude/Codex CLI on your machine
Image: Michielbdejong (auto-discovered)

A locum is a qualified professional who temporarily does someone else's job.

Lets Grok Bot delegate coding work to the Claude Code and Codex CLIs you are already logged into on your own machine, instead of burning Grok Bot usage on its own agent loop.

Not affiliated with or endorsed by xAI, Anysphere, OpenAI, or Anthropic.

Grok Bot runs on a persistent computer in xAI's cloud, so it cannot see localhost

. It reaches this server over a tunnel, as a custom MCP connector -- a documented Grok feature, not a workaround.

Grok Bot (xAI cloud)
   └─ MCP tool call ──► tunnel ──► this server (your Mac)
                                      ├─ spawns `claude -p`   (your Claude sub)
                                      └─ spawns `codex exec`  (your ChatGPT sub)

Anthropic's Claude Code legal page draws the boundary explicitly:

Advertised usage limits for Pro and Max plans assume

ordinary, individual usage of Claude Code and the Agent SDK.

Anthropic does not permit third-party developers to offer Claude.ai login or to

route requests through Free, Pro, or Max plan credentials on behalf of their users.

So:

| ✅ | You, your machine, your subscription, your own work | | ❌ | Hosting this so other people's requests hit your subscription | Sharing the code so others run it on their own subscription -- fine only while the invariants below hold |

Invariants. Do not remove them; they are the reason this is legal.

  • Never read credential files, keychains, or OAuth tokens.
  • Never call api.anthropic.com

/api.openai.com

directly. - Only ever spawn the official claude

/codex

binaries, authenticated by the operator through the vendors' own login flows. - Single-operator: one bearer token, one allowlist of workspace roots.

If a change would break one of these, it is the wrong change.

Requires claude

and codex

already signed in, plus uv.

cp .env.example .env          # then set a real token and your roots
set -a && source .env && set +a
uv run server.py

Tunnel it (Cloudflare quick tunnels do not carry SSE; this server uses Streamable HTTP, so they work fine):

brew install cloudflared
cloudflared tunnel --url http://127.0.0.1:8791

That quick tunnel is fine for a first run, but it hands out a new hostname on every restart and Grok stores the URL -- so you would re-register and re-consent every time. For anything ongoing, take a stable hostname instead (needs a domain already on your Cloudflare account):

cloudflared tunnel login          # browser, once
./setup-tunnel.sh locum.example.com
cloudflared tunnel run locum

setup-tunnel.sh

is idempotent: it creates the named tunnel if missing, points DNS at it, and writes ~/.cloudflared/config.yml

.

To keep the tunnel up across reboots, use the script, not cloudflared service install

:

sudo ./install-service.sh

cloudflared service install

writes a launchd plist containing only the binary path, with no tunnel run

subcommand, so the daemon crash-loops while your user-level tunnel quietly masks the failure. install-service.sh

writes the plist itself and verifies /health

before claiming success.

That keeps the tunnel up. To keep the server up as well, so a reboot does not leave a healthy hostname pointing at nothing:

./install-agent.sh        # no sudo: it must run as you

Two macOS requirements, both of which fail confusingly if missed:

  • If this checkout is under ~/Documents

,~/Desktop

, or~/Downloads

, grantFull Disk Access to. launchd agents do not inherit your terminal's TCC grants, and TCC judges the executable launchd starts.uv

  • Run claude setup-token

and put the result in.env

asCLAUDE_CODE_OAUTH_TOKEN

. A launchd agent does not get your login session's credential access, so delegation fails with "OAuth session expired" even though the server itself starts fine.

Register at grok.com/connectors

-> New Connector -> Custom, with the tunnel URL plus /mcp

.

Grok's custom connectors speak OAuth 2.1 only -- the dialog has no static header field. The bridge therefore ships its own minimal authorization server, so there is no third-party OAuth app to create. Grok discovers the endpoints via /.well-known/oauth-authorization-server

and self-registers over RFC 7591.

If Grok still shows the manual "OAuth Credentials Required" form, fill it as:

Field Value
Client ID anything, e.g. locum
Client Secret leave empty
Authorization Endpoint https://<tunnel>/authorize
Token Endpoint https://<tunnel>/token
Scopes mcp
Token Auth Method none (PKCE only)

You'll then get a consent screen. It shows the redirect target -- check it says grok.com

before approving -- and asks for a passphrase: paste your LOCUM_TOKEN

.

That passphrase gate is load-bearing. /authorize

sits on a public tunnel; without it, anyone who learned the URL could mint a token and get shell access to your machine.

A connector only makes the tools available. Without an instruction to prefer them, the Bot keeps grinding through its own loop and you save nothing. Two levers, and the weaker one is the one people reach for first.

1. The Bot's description (strongest). Create a dedicated Bot, then Bot actions → Edit Profile → Description. That field is for rules that should remain true, so it applies to every conversation without being invoked:

You have the `locum` connector, which delegates work to the operator's own
machine.

Any task touching a real repository (multi-file edits, refactors, debugging,
running tests, reading a codebase) must go to delegate_to_claude rather than
being done yourself.

delegate_to_claude returns a job_id immediately. Poll check_job about every 30s
and report recent_activity so progress is visible. Never re-delegate a job that
is still running. For follow-ups on the same work use resume_claude with the
session_id, never a fresh delegation.

cwd must be an absolute path inside an allowed root.

2. A saved Skill (the detail). SKILL.md

in this repo covers how to write a good delegation prompt and what to do when a job errors. Save it by asking a Bot "save this as a skill called delegate-to-locum" with the file contents pasted, then enable it under Settings → Plugins → Yours. Invoke explicitly with /

in the composer when you want it applied to a specific task.

Use both. The description guarantees the behaviour; the skill improves the quality of the prompts the Bot writes.

python3 test_oauth.py

Boots a throwaway instance on port 8799 and exercises discovery, dynamic registration, the consent gate, PKCE enforcement, single-use codes, token exchange, refresh, and an authenticated MCP initialize

. 13 assertions.

uv run --with fastmcp --with uvicorn python3 test_jobs.py

Covers the job registry: ordering, status filtering, limits, truncation, and the LOCUM_MAX_JOBS

cap. 18 assertions. Neither suite needs claude

installed.

Tool Purpose
delegate_to_claude(prompt, cwd, model?, effort?)
Start a Claude Code job. Returns job_id immediately.
resume_claude(session_id, prompt, cwd?, model?, effort?)
Continue a session. Reuses the prompt cache -- always prefer for follow-ups.
delegate_to_codex(prompt, cwd, model?, effort?)
Same contract, via Codex CLI.
check_job(job_id)
Poll. Returns status, turn count, recent tool activity, result.
list_jobs(limit?, status?)
Recent jobs, newest first. Confirms work really ran, recovers a lost job_id , finds a session_id to resume.
cancel_job(job_id)
Kill a runaway job.

Everything is async. MCP tool calls time out long before a real coding task finishes, so delegate_*

returns a handle and the Bot polls. This is the single thing that makes the integration work at all.

effort

takes one vocabulary across both CLIs, so a caller never has to know which vendor spells it which way:

effort | Claude | Codex | |---|---|---| low / medium / high | --effort <level> | -c model_reasoning_effort="<level>" | max | --effort max | -c model_reasoning_effort="high" (no distinct max) |

model

passes through unvalidated, since vendors add models faster than any allowlist survives. Claude takes aliases (opus

, sonnet

, fable

) or full names; Codex takes its own.

Both are optional and both cost real quota, so the skill tells the Bot to raise them deliberately: high effort for architecture, subtle debugging, and anything touching auth or data loss, and nothing for mechanical edits. check_job

and list_jobs

echo what was actually used.

https://your-host/dashboard

shows every delegated session: what was asked, which model and effort ran it, the full transcript of tool calls, reasoning and output, token counts, cost, and duration. Running jobs stream in live over server-sent events, so it doubles as a window onto work happening right now.

Sign in with the same LOCUM_TOKEN

. It is exchanged for an HttpOnly cookie, so there is no second secret to manage and revoking the token revokes dashboard access at the same moment. Everything under /api/

and /dashboard

requires that cookie.

This is also the answer to "how do I show that it is really running on my machine": the MCP client shows a chat, the dashboard shows the actual tool calls and token spend behind it.

The server narrates delegated jobs on stdout, so a terminal beside your MCP client shows what is actually running:

06:51:24  -> claude  1726b9229609  sonnet  ~/Documents/Projects/locum
06:51:24       "How many tools does this MCP server expose? Read server.py..."
06:51:30       Bash grep -c "@mcp.tool" ...
06:51:33       Bash grep -n "@mcp.tool" ...
06:51:35  ok claude  1726b9229609  done - 4 turns - 10.3s - $0.17

Under launchd that goes to ~/Library/Logs/locum/server.out.log

:

tail -f ~/Library/Logs/locum/server.out.log | grep -v 'INFO:'

Set LOCUM_NARRATE=0

for access logs only. The log has no rotation, so on a long-running install either turn narration off or truncate it periodically.

Locum runs agents autonomously by default, because a delegated job has nobody at the keyboard: an approval prompt does not the work, it hangs the job until it times out. LOCUM_AUTONOMY=bypass

passes --dangerously-skip-permissions

to Claude and --dangerously-bypass-approvals-and-sandbox

to Codex.

Be clear-eyed about what that buys and costs. The agent can run any command as your user. LOCUM_ROOTS

bounds the directory a job starts in, and it is still the check that stops a caller pointing a job at ~/.ssh

, but a shell command the agent runs is not confined by it.

What actually protects you, in order:

LOCUM_TOKEN

, which gates both the consent screen and every MCP callLOCUM_ROOTS

, kept narrow- Running this only for yourself, on your own machine

LOCUM_AUTONOMY=ask

restores prompting, but only use it from a client that can surface the prompts. Grok Bot cannot, so jobs will hang.

Every token comparison uses hmac.compare_digest

. Authorization codes are single-use and expire in 120s. PKCE S256

is required -- plain

is refused. /health

and the discovery documents are the only unauthenticated routes; LOCUM_TOKEN

itself also remains a valid bearer token, which is what makes curl

smoke tests work.

Everything 404s, including /health, but the tunnel says it is connected. Port collision. The Grok Bot desktop app listens on

[::1]:8787

, and macOS resolves localhost

to ::1

before 127.0.0.1

-- so an ingress pointed at http://localhost:8787

silently reaches Grok Bot instead of the bridge, and Grok Bot answers Not found.

This is why the default port is 8791 and why the ingress rule uses

127.0.0.1

, never localhost

. To confirm:

lsof -nPw -iTCP:<port> -sTCP:LISTEN    # who actually owns the port
curl -s http://127.0.0.1:<port>/health # locum answers {"ok": true}
curl -s http://localhost:<port>/health # if this differs, you have a collision

./demo-port-collision.sh

reproduces the whole thing in isolation on a port of your choosing, if you want to see the mechanism without waiting to be bitten by it.

cloudflared --loglevel debug tunnel run <name>

settles it: each request logs ingressRule=

and originService=

, so you can see whether the 404 came from cloudflared's catch-all or from whatever is actually on the port.

Jobs fail with "OAuth session expired and could not be refreshed". Check the boring cause first: run claude -p "reply with OK"

yourself. If that fails too, your Claude Code login has genuinely expired and claude /login

fixes it. Locum surfaces the CLI's error verbatim, so this looks identical to a Locum bug.

If claude -p

works standalone but fails through Locum, then the server was launched from inside a Claude Code session. Claude Code exports CLAUDECODE

, a CLAUDE_CODE_*

family, and ANTHROPIC_BASE_URL

into every child process; a claude

that inherits them believes it is a nested child session and tries to delegate auth to a host socket that is not listening. _child_env()

strips those when nesting is detected, but some sandboxed hosts broker Claude's credentials entirely in-process, and there a spawned claude

has nothing on disk to authenticate with no matter what the environment says. Run the server from an ordinary terminal.

Cloudflare returns 403 with error code: 1010. Cloudflare bans the default

Python-urllib

User-Agent signature. Only that signature -- curl, Go, Node, okhttp, and an absent User-Agent all pass, so MCP clients are unaffected. Set a User-Agent on any Python tooling you point at the tunnel:

urllib.request.Request(url, headers={"User-Agent": "locum-check/1.0"})
  • Cuts Grok Bot usage, does not zero it -- orchestration turns still meter. The win is collapsing ~50 Bot steps into one tool call plus a few polls.
  • Your machine must be awake with the tunnel up.
  • Quick-tunnel URLs change on restart; ./setup-tunnel.sh

gives you a stable hostname so the connector survives. - Cold delegation re-pays ~18k tokens of CLAUDE.md

  • system prompt setup.resume_claude

avoids it.

How it works, the architecture writeup, with diagrams and the three bugs that cost the most timeCONTRIBUTING.md, development setup and the four invariantsSECURITY.md, threat model and how to report a vulnerability. Read this before exposing Locum to a tunnelCHANGELOG.md

Licensed under Apache 2.0.

── more in #ai-tools 4 stories · sorted by recency
── more on @locum 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-locum-grok-b…] indexed:0 read:11min 2026-08-22 ·