cd /news/ai-agents/show-hn-run-claude-frontend-with-non… Β· home β€Ί topics β€Ί ai-agents β€Ί article
[ARTICLE Β· art-147972] src=github.com β†— pub= topic=ai-agents verified=true sentiment=Β· neutral

Show HN: Run Claude Frontend with Non-Anthropic Models

A developer released a local proxy that translates Anthropic's Messages API to OpenAI-compatible backends, letting the stock Claude Code CLI run against OpenAI keys or on-device models such as Ollama's qwen2.5:7b-instruct. The proxy, run via `node supervise.mjs` on 127.0.0.1:8123, was verified end-to-end with the unmodified CLI on a full agent turn using 221 tools, with logs showing model=claude-opus-4-8->gpt-5.6-sol. The accompanying desktop build repoints Anthropic's unpacked Claude Desktop app under a stock Electron runtime but requires users to supply their own licensed copy of the ~40 MB proprietary bundle, which is not redistributable.

read18 min views1 publishedOct 9, 2026
Show HN: Run Claude Frontend with Non-Anthropic Models
Image: Michielbdejong (auto-discovered)

Claude Code and Claude Desktop are good agent harnesses. This repo lets you drive their agent layer with a backend of your choice β€” an OpenAI key, an on-device model on your own GPU, or stock Anthropic β€” by translating between the Anthropic and OpenAI APIs locally. The chat window is always the real claude.ai web app; only the agent sub-layer (Claude Code) is repointed.

The desktop build's Code tab. The model picker lists Composite (the cross-provider fallback chain) plus every keyed provider's models as provider:model β€” Gemini, Mistral, nvidia, ollama-cloud, llm7, freetoken, cloudflare β€” each routing that turn to its own upstream through the proxy; the local usage dashboard summarises agent activity.

Two independent things live here, and you may only want the first:

What it is What you need
1. The proxy β€”openai-proxy/ A local server that speaks the Anthropic Messages API on the front and callsOpenAI (or any OpenAI-compatible server) on the back. Point any Anthropic-API client at it. Node 22+ (native fetch ), and an OpenAI keyor a local server like Ollama
2. The desktop build Anthropic's Claude Desktop app, unpacked and run under a stock Electron runtime, with the proxy wired in, a provider switch, and a settings GUI Linux or macOS, plus your own copy of Claude Desktop

The proxy is the generally useful part: it works with the ordinary claude CLI on any machine, and needs nothing from this repo's app/ directory. Everything below the Desktop build heading is specific to running Anthropic's Electron app.

Licensing, read this first. app/ is Anthropic's unpacked proprietary bundle (~40 MB of their JavaScript, native binaries and resources). It is not redistributable, which is why this repository is private. For part 2 you must supply that directory from your own licensed Claude Desktop install β€” see Supplying the bundle. Nothing in part 1 touches it.

export OPENAI_API_KEY=sk-...              # or point at a local server; see below
cd openai-proxy && node supervise.mjs     # listens on 127.0.0.1:8123, restarts on crash

node proxy.mjs still works and is right for a one-off. Prefer the supervisor for anything you leave running: the proxy has taken itself down on a dropped upstream socket with nothing noticing for hours β€” the app, the launcher and every agent kept running against a closed port. The supervisor restarts it, logs why, and gives up loudly rather than looping if it cannot start.

Point the stock Claude Code CLI at it:

ANTHROPIC_BASE_URL=http://127.0.0.1:8123 ANTHROPIC_API_KEY=unused claude -p "hello"

ANTHROPIC_API_KEY must be set to something because the CLI insists on it, but the proxy ignores it and authenticates to OpenAI with OPENAI_API_KEY.

Verified end-to-end with the unmodified CLI: a full agent turn, 221 tools, tool calls executed and results fed back, with the proxy log showing model=claude-opus-4-8->gpt-5.6-sol. The CLI still says "Opus" because it hardcodes that in its own prompt; the proxy log and the ANTHROPIC_BASE_URL in the process environment are the ground truth.

Set OPENAI_BASE_URL at any OpenAI-compatible server (Ollama, llama.cpp, LM Studio, vLLM) and the proxy translates to it exactly as it does to OpenAI. A loopback base URL is treated as keyless β€” no OPENAI_API_KEY needed (a placeholder bearer is sent, which local servers ignore):

OPENAI_BASE_URL=http://127.0.0.1:11434/v1 OPENAI_MODEL=qwen2.5:7b-instruct OPENAI_API=chat \
  node openai-proxy/proxy.mjs

The desktop build wires this up as the local provider, which also starts and tunes Ollama for you.

Everything has a default; set only what you want to change.

Variable Default What it does
OPENAI_API_KEY β€” Required for a remote endpoint; optional for a loopback one . Also read from.openai-key (see below).
OPENAI_MODEL gpt-5.6-sol Any model id. Names containing codex route toResponses automatically; anything else lands on Chat Completions unlessOPENAI_API=responses is set.
OPENAI_BASE_URL https://api.openai.com/v1 Any OpenAI-compatible endpoint, including a local one.
PORT 8123
OPENAI_API responses responses orchat , overriding the name heuristic.Required for a non-codex remote model : this app sends 200+ tools and Chat Completions caps at 128.
OPENAI_REASONING_EFFORT max The proxy steps down to the highest value your model accepts.
OPENAI_CLASSIFIER_SAFETY_MODEL gpt-5.4-2026-03-05 Model for Claude Code's auto-mode safety verdict. It has a 60-second deadline and denies the action when it expires, so this wants a fast model β€” seeopenai-proxy/README.md for the measurements.

The full list β€” around thirty knobs, each documented with the bug that motivated it β€” is in openai-proxy/README.md.

Instead of environment variables, put KEY=VALUE lines in .openai-model at the repo root (checked in, for non-secrets). The secret API key goes in its own gitignored file, .openai-key ( apiKey=…; copy .openai-key.example to start). Precedence is env var β†’ .openai-model β†’ .openai-key β†’ default.

That precedence lives in exactly one place, openai-proxy/config.mjs, which also produces the config hash below. To see what a launch would actually use:

node openai-proxy/config.mjs            # the effective config, with the source of each value

--hash prints just the config hash; --validate checks it and exits non-zero on an error. The key never appears in any of them β€” only a sha256: fingerprint of it.

/health reports an identity, not just liveness:

{ "ok": true, "instance": "8f3c…", "pid": 82110, "configHash": "38d9c8b2…",
  "codeVersion": "a8e08f01…", "inflight": 0, "model": "gpt-5.6-sol", "…": "…" }
  • instance is a nonce generated at startup and also written to proxy-runtime.json . Only the process that generated it can serve it, so matching both proves ownership β€” which is what lets the launcher restartits proxy and refuse to touch anything else.
  • configHash covers every behaviour-affecting setting plus a fingerprint of the key. The launcher compares it and restarts a proxy serving stale settings, instead of the old behaviour where any answer on/health counted as healthy and a changed model silently did not apply.
  • codeVersion is a hash of the proxy sources, catching a process running older translation logic with current settings.
  • inflight is how many turns are being answered right now, so a restart can say what it would interrupt.

Supervisor behaviour is tuned by environment variable; the defaults are meant to be left alone. PROXY_MAX_FAST_FAILURES (8) bounds immediate restart attempts, PROXY_RESTART_BASE_MS (500) and PROXY_RESTART_MAX_MS (30000) set the backoff, and PROXY_HEALTH_EVERY_MS (15000) / PROXY_HEALTH_TIMEOUT_MS (5000) / PROXY_HEALTH_MISSES (3) control the watchdog that replaces a proxy holding the port without answering. An externally-killed proxy always restarts and never counts toward the give-up bound.

Chat Completions Responses
Tool limit 128 (129 β†’ HTTP 400) none observed β€” probed to 512
Reasoning controls none reasoning.effort , reasoning summaries
Local-server support universal recent Ollama; not all local servers

Claude Code offers 27 tools; Claude Desktop offers over 200. On the chat surface the 128-tool cap is real, so the proxy keeps the essential tools (read/write/edit/run/search/plan/web, plus renderers) and fills the remainder in the agent's own order, logging what it dropped β€” a silently truncated tool list is indistinguishable from a model that just declined to use a tool.

Text and tool calls in both directions, streaming and non-streaming, plus fixes for behaviour differences between the two model families:

  • Rendering β€” GPT models emit\(…\) /\[…\] for maths, which Claude's renderer shows literally. Rewritten to$…$ /$$…$$ , fence-aware so code samples stay verbatim.
  • Persistence β€” GPT models routinely end a turn to check in ("If you want, I'll run that now"), which in an agent loop reads as a stall. An explicit persistence directive plus auto-continue when a turn announces an action and calls no tool.
  • Context overflow β€” Claude Code sizes its own auto-compaction from the model itthinks it is talking to, so it never fires. The proxy compacts and retries, including when the overflow arrives as a mid-stream event rather than an HTTP 400.
  • Empty and truncated turns β€” retried or resumed rather than surfaced as a blank reply.
  • Unsupported parameters β€” the CLI sendsstop_sequences , which some models reject. The proxy drops whichever parameter the API names and retries, so the next unsupported knob self-heals too.
  • Thinking β€” OpenAI reasoningsummaries are mapped to Anthropic thinking blocks. Raw chain-of-thought is not available from the API at any setting.

Images work. Anthropic {type:"image", source:{…}} blocks β€” base64 or url β€” become image_url on Chat Completions and input_image on Responses. If the model has no vision the picture is dropped and the question kept, with an honest note in its place, rather than failing the turn.

Known gaps: /v1/messages/count_tokens is estimated, and the cost figures the CLI prints are computed from Anthropic's price list and are meaningless when proxied.

npm run eval           # run the corpus, diff against the frozen baseline
npm run eval:freeze    # overwrite the baseline with what the code does NOW

eval/ holds a synthetic, network-free corpus. Each case runs through the real proxy against a fake upstream, and what gets recorded is the payload the proxy decided to send: resolved model, tool count, whether a late tool survived, tool_choice, hint injection, reasoning, verbosity, output cap, cache key, pricing tier. Every one of those decisions is settled before a token is generated, which is what makes it a baseline rather than a sample. npm test checks the frozen baseline on every run, so a behaviour change cannot arrive unnoticed inside an unrelated commit. Alongside it are invariants the baseline is not allowed to encode away β€” a classifier is never given tools, a safety verdict never resolves to the main model, an agent turn that merely quotes the classifier contract keeps its full catalogue.

npm test        # the whole suite: proxy + settings + launcher scripts + eval baseline

Most were written against a specific misbehaviour; the comments say which.

Runs Anthropic's Claude Desktop (@ant/desktop v1.24012.9, an Electron Forge + Vite build) from its unpacked app.asar under a stock Electron runtime, with the proxy wired into the agent layer, a provider switch, and a settings GUI over the launcher's config. Runs on Linux and macOS.

app/ is committed in this repository β€” Anthropic's code β€” and that is exactly why it is private and must not be published. node_modules/ and app/node_modules/ are not committed; you populate them from your own licensed install.

To rebuild app/ from your own install:

  1. Find app.asar in your Claude Desktop installation:
  • Linux:/usr/lib/claude-desktop/resources/
  • macOS:/Applications/Claude.app/Contents/Resources/
  1. npx @electron/asar extract app.asar app/
  2. Merge the sibling app.asar.unpacked/node_modules/ back in overapp/node_modules/ β€” the native addons (@ant/claude-native ,node-pty , and on macOS@ant/claude-swift ) live there as platform binaries and the app expects them beside their JS. On Linux,@ant/claude-swift is absent by design and fails closed.

The version here is @ant/desktop v1.24012.9; a different version will not necessarily match the env-gated patches, which are keyed to strings in this build.

npm install        # stock Electron 42.9.2, once
./run.sh

Electron 42.9.2 is pinned to match the native modules harvested from a current Claude Desktop install. A window opens on the claude.ai login screen; sign in as usual. run.sh re-creates the resources/app.asar β†’ app/ symlink and reinstalls the disclaimer helper on every launch, so reinstalling node_modules costs nothing.

Linux note: npm install downloads the Electron binary from GitHub's release CDN. If that host is blocked on your network, use a mirror:

ELECTRON_MIRROR=https://registry.npmmirror.com/-/binary/electron/ \
  ELECTRON_CUSTOM_DIR="v{{ version }}" node node_modules/electron/install.js

run.sh is cross-platform (uname -s): it resolves the Electron resources dir ( dist/resources on Linux, Electron.app/Contents/Resources on macOS), the Claude Desktop profile (~/.config/Claude vs ~/Library/Application Support/Claude), and the disclaimer helper path per platform. See LINUX.md for the port's design and the folded-conditional gotchas.

On modern Ubuntu (kernel.apparmor_restrict_unprivileged_userns=1) Electron's namespace sandbox is blocked, so it needs a setuid-root chrome-sandbox β€” which the downloaded Electron isn't. run.sh self-heals this by symlinking to the setuid-root chrome-sandbox the Claude Desktop .deb already installed (same Electron build, byte-identical), keeping the renderer sandbox on with no sudo. If no system copy matches, it prints the one-time sudo chown root:root … && sudo chmod 4755 … fix.

Stock Electron has no equivalent of Claude Desktop's TCC-attribution helper, and the app invokes it as disclaimer <command> <args…> β€” which makes it the one supported boundary at which this repo can choose the agent's internal model identity. The helper is repository-owned source (scripts/claude-code-disclaimer.sh), installed as an absolute symlink by scripts/install-disclaimer.mjs at the per-platform Helpers/ path. In OpenAI/local mode it rewrites the bundled Claude Code executable to the configured [1m] capability identity; in Anthropic mode it is an exact argv passthrough. It also exports CLAUDE_CODE_SUBAGENT_MODEL so Task/ Explore/teammate spawns β€” which run inside the session process and have no argv to rewrite β€” get the right model. Nothing patches the Claude Code binary or the app bundle.

Linux or macOS, and Node β‰₯ 22 (Electron 42 bundles its own Node for the app itself). The app refuses to start below Electron 34.

./settings.sh

Every parameter, grouped, each with the reasoning behind it, plus live status and Save & restart. The status line separates configured from active: a proxy serving previous settings is labelled STALE β€” restart to apply; a launch-time OPENAI_MODEL=x ./run.sh override is shown as an override; and in Anthropic mode the OpenAI-only settings are dimmed as not in effect. Save & restart stops the app (it holds an exclusive LevelDB lock on the session store), waits for exit, stops the proxy only if it can prove the proxy is ours, relaunches, and confirms the new proxy reports the expected config hash before reporting success. It is a local 127.0.0.1 page requiring a per-start token, because the API writes config and can restart the app.

Two modes: anthropic (Claude, direct) or proxy (via the local translation proxy). In proxy mode a single DEFAULT_PROVIDER (in .provider) picks the upstream that backs the default turns, the background classifier and compaction β€” one of openai, local (on-device Ollama), openrouter, cohere, gemini, mistral, groq, ollama (Ollama Cloud) β€” and any individual turn can run on a different provider by picking a <provider>:<model> from the Code-tab model dropdown (the proxy routes that turn to the named provider's key in .openai-key).

./run-proxy.sh        # via the local translation proxy β€” upstream = DEFAULT_PROVIDER in .provider
./run-anthropic.sh    # Claude, calling Anthropic directly (stock behaviour)
./run.sh              # whichever .provider says (default: proxy, upstream openai)

An old PROVIDER=openai|local|openrouter|cohere|gemini|mistral|groq|ollama (in .provider or on the command line) still works β€” it selects proxy mode with that provider as the default upstream.

anthropic proxy
agent's ANTHROPIC_BASE_URL https://api.anthropic.com http://127.0.0.1:8123
translation proxy not started started, health-checked
backend the proxy calls β€” the DEFAULT_PROVIDER upstream (OpenAI / on-device Ollama / OpenRouter / Cohere / Gemini / Mistral / Groq / Ollama Cloud), plus any provider picked per-turn
API key your Anthropic login per upstream β€” .openai-key (remote) or none (loopback Ollama)
model Claude, as shipped the upstream's model, or a <provider>:<model> chosen in the dropdown

Switching needs no un-patching: every edit to the bundle is env-gated (PROXY_ANTHROPIC_BASE_URL || <original host>), so with the variable unset the app uses its own Anthropic host. This affects only the agent β€” the chat window is the remote claude.ai web app talking to Anthropic in every mode.

DEFAULT_PROVIDER=local points the proxy at a local OpenAI-compatible server instead of api.openai.com, so the agent runs on this machine's GPU with no API key. It uses Ollama, configured in .local-model. The Code-tab dropdown also lists your installed Ollama thinking models as local:<model> (discovered at launch), so you can run a single turn on-device from any mode.

It manages Ollama for you. The agent's prompt (system + up to 128 tools) is large, and a system Ollama is usually pinned to a small context by its service unit β€” which would silently truncate that prompt, and can't be rebound without root. So run.sh starts its own Ollama on a side port (OLLAMA_MANAGED_PORT, default 11435) with a big context, sharing the models you already pulled, and leaves the system Ollama untouched for its other clients. It reuses that instance across launches and restarts it only when the context changes. The GPU tuning that makes a big context fit a laptop card is applied automatically: q8_0 KV cache (β‰ˆhalf the VRAM of f16), flash attention, and OLLAMA_NUM_PARALLEL=1 so the full context goes to the single agent request instead of being split across slots. Set OLLAMA_AUTOSTART=0 to manage the server yourself.

  • Context is per model.OLLAMA_CONTEXT_LENGTH is the default;CONTEXT_<model> overrides it per model (a bigger native window can take more; a large model needs less to fit VRAM). The compaction window follows automatically β€”run.sh derivesCLAUDE_CODE_AUTO_COMPACT_WINDOW as 3/4 of the context in effect, so switching models keeps a sane window (override per model withCOMPACT_<model> , or globally with an explicitCLAUDE_CODE_AUTO_COMPACT_WINDOW ).
  • A tool-calling model is required. The agent is tool calls end to end, so the model must emit proper OpenAItool_calls , not dump them as text.qwen2.5:7b-instruct (32K),qwen3:8b (40K) andgranite4.1:8b (128K) do;qwen2.5-coder:7b does not. Pull it first (ollama pull <model> ).

local speaks Chat Completions by default (universal across local servers; its 128-tool cap also helps a small model fit context). A 7-8B model on a laptop GPU is far less capable than the hosted models β€” this is for privacy/offline use, not maximum quality.

Dot files at the repo root, all read by run.sh (or the proxy), each overridable by an env var of the same name.

File Setting Default Effect
.provider PROVIDER openai openai = agent via the proxy to api.openai.com;local = proxy β†’ on-device GPU model;anthropic = agent calls Anthropic directly
.openai-model OPENAI_MODEL and friends gpt-5.6-sol Everything in the proxy config table above, for openai mode
.local-model OPENAI_MODEL , context, Ollama tuning Ollama qwen2.5:7b-instruct The local server, model, per-model context and GPU tuning for local mode
.openai-key(gitignored) apiKey β€” The secret OpenAI key for openai mode. Not read inlocal /anthropic . Copy.openai-key.example to start
.privacy DISABLE_TELEMETRY 1 Turns off first-party telemetry and Sentry, and sinkholes the analytics hosts
.sync SYNC_CLAUDE_SESSIONS ,SYNC_CLAUDE_GROUPING 1 Shares Claude Desktop's sessions and sidebar grouping with this build

user-data/ is an isolated profile with one deliberate exception: claude-code-sessions is a symlink to the real install's store, so sessions are genuinely shared and written in both directions (issue #3). A session created or renamed in either app is immediately the other's β€” and deleting one deletes it for both. Everything else in user-data/ (cookies, caches, Local Storage) stays private to this build. Local Storage deliberately is not shared: LevelDB permits one process at a time, so a shared directory would stop the second app from opening its own UI state.

The app runs unpackaged (app.isPackaged === false).

Works: launches and stays up on Linux and macOS, renders the real Claude web UI, native addons load, the desktop↔web IPC bridge is valid on the claude.ai origin, the agent runs through the proxy on OpenAI or a local GPU model, telemetry is off (the isolated-segment beacon shows ERR_BLOCKED_BY_CLIENT), and sessions sync with the real install.

Cannot work outside the signed bundle β€” auto-update, claude:// deep links, and anything gated on macOS entitlements the stock Electron binary does not carry (the VM/"virtualization" features, which the app reports as unavailable). On Linux the macOS-only pieces (global hotkeys, memory-pressure telemetry from @ant/claude-swift, computer-use frame streaming) are absent by design and fail closed; the launcher polyfills the few macOS-only Electron APIs the bundle calls unconditionally at startup so it boots cleanly.

Alarming lines appear on every launch, including healthy ones β€” startup races, an @ant/claude-swift/office365 ERR_MODULE_NOT_FOUND (macOS-only modules, fail closed), Wayland binding warnings, and MISSING_TRANSLATION / "Cowork not supported" notices. To tell whether a launch actually succeeded, grep for these:

grep -c "isLoggedOut: false" boot.log      # signed in
grep -c "loaded .* features" boot.log       # feature flags fetched

Delete user-data/.

Document What's in it
openai-proxy/README.md The proxy in depth: every knob, every behaviour fix and the bug that motivated it, the classifier measurements, the compaction ladder
SESSIONS.md How the desktop build relates to your real Claude install: shared memory, session sync, and why grouping behaves as it does
ARCHITECTURE.md How the desktop app is built: which UI is local vs. remote, the IPC bridge the remote page uses, which layers can be modified
ANTHROPIC_ENDPOINTS.md Every Anthropic endpoint the app calls, with payloads and what each is for
FINDINGS.md Notable things in the bundle β€” permission modes, the extension registry, companion-hardware pairing, the auto-approved tool list
LINUX.md The Linux port: native-module harvest, folded-conditional blockers, and what degrades off macOS
OPENROUTER.md The OpenRouter provider: catalog analysis on a free-tier key, the 11-model free allowlist, quality/speed ordering, and how the allowlist gates the pickers and gateway discovery

The proxy is self-contained (openai-proxy/, plus settings/ for the GUI) and carries no Anthropic code, so it can be developed and shared independently of the bundle. If you extend it, add a test naming the behaviour you fixed β€” that is the convention throughout, and it is what caught several regressions in the fixes themselves.

── more in #ai-agents 4 stories Β· sorted by recency
── more on @claude code 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-run-claude-f…] indexed:0 read:18min 2026-10-09 Β· β€”