Repository navigation
Releases: abdurrehmandaudi/justdowork-proxy
Release list
v3.0.0 — remembers what matters, sees the whole file, stops wasting seconds
ccproxy v3.0.0
A proxy that lets Claude Code run against a third-party relay
(https://api.justwoker.icu, model claude-opus-4-8) that speaks the Anthropic
Messages API but not properly.
Claude Code thinks it is talking to Anthropic. The proxy translates.
export ANTHROPIC_BASE_URL='http://127.0.0.1:8181'
export ANTHROPIC_API_KEY='dummy'
claudeWhat is in this release
Three problems, each found in a real log and each measured after the fix.
1. It forgot the task after two prompts
trim_history() kept messages[0] and the newest messages that fitted, and
deleted everything in between silently. From a real log:
REQ #843 | client_msgs=847 sent_msgs=168
REQ #844 | client_msgs=849 sent_msgs=167
...
REQ #848 | client_msgs=857 sent_msgs=161
851 messages in, ~165 out, 690 gone, on every request of that session. Across
the whole log: 648 of 1315 requests (49%) had history trimmed.
It also measured messages at their raw size, while the tool output inside them
was about to be cut to max_tool_result_chars anyway — so the budget looked
full when it was not. On a session of 12k-char tool results the old code fitted
18 messages; this one fits 148.
The model, seeing an original task and then a disconnected recent tail, answered
a question nobody had asked, and at one point replied with the user's own last
message as if it were its own answer.
Now: the human's own turns get a reserved slice of the budget
(history_user_reserve), the live working set is always kept
(keep_recent_messages), and anything still dropped leaves a marker at the cut
plus a Context notice in the system prompt. The model knows the conversation
is incomplete and says so instead of guessing. The log prints dropped=N.
2. It only fixed the surface
Not a personality problem — a config value. max_tool_result_chars was 4000,
so every tool result was truncated to 4,000 characters. A Read of a
30,000-char file reached the model as its first 4,000, and it then edited a file
whose end it had never seen.
Raising it is cheap: Claude Code itself replaces every older tool result with a
~28-character first line, so only the newest one is ever large. It is now
32000.
Tested on a real 30,256-character file with the bug at character 29,853:
REQ #1-2 hist=2,375 -> 2,430 the task
REQ #3 hist=11,326 (+8,896) the whole file, forwarded
REQ #8 hist=12,356 the answer
The model named the exact function and line, changed only the two wrong
branches, and reported that the thirty helper functions around it were "just a
distraction, there was no bug in them". Eight turns, 57 seconds, every
request first-try, nothing trimmed. The test passed afterwards.
WORKING_RULES also gained a Look before you change section: fix the cause,
not the symptom; read the whole file and its callers first; say what you looked
at.
3. It was slow
95% of wall time is the relay generating. Measured across 626 turns: median
throughput 29.5 tokens/s, median turn 10.3s, and 146.5 of 154 minutes
spent waiting on output tokens. Input is nearly free (6k extra input tokens ≈
+1.4s) and prompt caching changes nothing. That part is not fixable here.
What was fixable, in one real log of 1320 upstream attempts:
940 x 200 349 x 503 28 x 403 3 x 524
The relay drops ~26% of requests with an empty 403/503, at random, with
no cooldown — a retry sent at 0.0s after a 403 came back 200. The old
code slept a fixed 2 seconds before its first retry. Across the log 194
retries fired: 7–16 minutes of dead sleep, plus a full payload re-sent each
time.
Now the first retry is immediate, 403 is retryable, and there are three cheap
attempts. Measured through the proxy: 30/30 requests succeeded, where the
old policy lost about one in five.
[22:15:12] upstream attempt 1 -> 503 (empty body)
[22:15:12] upstream attempt 2 -> 403 (empty body)
[22:15:12] upstream attempt 3 -> 200
Also: a reply asking for three web searches used to make three round-trips,
one at a time. They now run together and come back in one message — 3 × 0.3s of
work finishes in 0.31s.
Extended thinking
The relay does support it ({"thinking":{"type":"enabled","budget_tokens":N}}
returns a real signed thinking block). But thinking tokens are output tokens,
so a 2,000-token budget costs ~68 seconds at 29.5 tok/s. So it is
adaptive: a budget on the turn that plans (a fresh user message), none on
the turns that only execute it (each starts with a tool_result). A budget the
client asks for is forwarded and its blocks are returned; a budget the proxy
decided on stays hidden.
thinking_enabled: false turns it off entirely.
Prompt caching — present, and doing nothing
cache_system_prefix puts a cache_control: {"type": "ephemeral"} breakpoint on
the last system block, which is where Anthropic's own clients put one (the cache
prefix runs tools → system → messages, so it covers the tool schemas too).
The relay accepts it and answers with a cache_creation_input_tokens /
cache_read_input_tokens block — which is why this looked like it worked. It
does not. Measured directly against the relay on 2026-10-07, sweeping the
cacheable prefix from 131 to 61,459 chars:
lines sys chars cache? latency in_tok cache_write cache_read
2 131 no 3.27s 10409 10407 None
200 13569 yes 3.28s 15161 4881 10278
900 61459 no 5.13s 31961 31959 None
20 1336 yes 2.51s 10841 561 10278
- The read is always 10,278 tokens. 1,336 chars and 13,569 chars both come
back as 10,278 — so it is the relay's own hidden prefix, never yours. Your
system prompt, tool schemas and history are never served from cache. - The write is
input_tokens - 2. On every request with nocache_control
at all:cache_write = 15159againstin_tok = 15161. That is "everything was
a write", which measures nothing. - It arrives at random. A working cache writes on request 1 and reads on
2..N. This one showed up on 1 of 8 requests through the proxy — and that one
was the first. - Nothing gets faster. The requests reporting a read were not quicker than
the ones that did not.
Live, through the proxy, eight identical requests:
cache_hit_reqs 1
cache_read_tok 10278 <- the same 10278 as always
cache_write_tok 109730 <- 15001 = in_tok - 2, seven times over
The breakpoint is kept — it costs nothing and would start helping if the relay
ever implements the cache properly. What the proxy does not do is pass the
relay's invented cache_creation_input_tokens on to Claude Code: telling it a
cache absorbed 15,001 tokens on every single request would be a lie. The read
is forwarded when one appears.
The dashboard gained a Cache reads card (total, plus how many of your
requests saw one) and a cache pill per hit, so this stays visible instead of
being something you have to go and re-measure.
A bug this turned up: the request record was built from the client-facing usage
block, which deliberately has cache_creation_input_tokens stripped — so the
dashboard read 0 cache writes on a relay that sends that field every time.
The raw upstream usage is now carried on the request context and read from there.
Earlier fixes still in the tree
- Multi-agent / Monitor / Write / Edit calls were silently dropped. The model
writes JSON with literal newlines and invalid escapes; a strictjson.loads
failed and the whole tool call vanished.Monitorwas dropped 3049 times
in onedebug_dump. Now a 4-step repair (strict → literal control chars →
escape repair → bracket balancing) recovers it, and valid JSON is untouched. - 524 timeouts. Claude Code sends the entire session every request; one dump
was 1,650,689 chars / ~1,303 messages. The relay 524s on that. The proxy now
trims to a budget. - Web search / web fetch. Claude Code cannot run these itself against a
third-party base URL, and neither does the relay. The proxy runs them: live
DuckDuckGo search, page fetch, image fetch. - Native tools.
Read/Write/Edit/Bashare mapped to the relay's
lowercase native names (read/write/edit/bash) and sent in the payload's
toolsarray. Everything else uses the<tool_call>text protocol. - Every tool result was being deleted —
_is_empty_content()looked for a
textfield, but atool_resultcarries its payload incontent. So every
tool result looked empty and was dropped, and the model called the same tool
again forever.tool_use,tool_result,image,documentand
search_resultnow all count as real content.
Install
Requirements
- Python 3.8 or newer — that floor comes from
flask>=3.0. The proxy code
itself uses nothing newer than 3.6 syntax, and compiles clean on 3.12. - Git (only if you clone; the ZIP download works too)
- An API key for the relay
macOS
Easiest — the start script creates the virtualenv and installs everything on
its first run, so there is nothing to do by hand:
git clone https://github.com/abdurrehmandaudi/justdowork-proxy.git
cd justdowork-proxy
sh start.shIf you would rather do it yourself:
python3 -m venv .venv
.venv/bin/python3 -m pip install -r requirements.txtIf python3 is missing, install it with Homebrew:
brew install pythonWindows
Easiest — in PowerShell, the start script does the same thing on first run:
git clone https://github.com/abdurrehmandaudi/justdowork-proxy.git
cd justdowork-proxy
start.batIf you would rather do it yourself:
python -m venv .venv
.venv\Scripts\python.exe -m pip install -r requirements.txt...
v1.0.1 — PolyForm Noncommercial license
Adds a license. The code is unchanged — this release exists so the terms are
clear and unambiguous.
License: PolyForm Noncommercial 1.0.0
The repository previously had no license file, which left the terms
ambiguous — people assume "public on GitHub" means "free to do anything with".
Now it is explicit.
Free to use for: personal use, hobby projects, study, research, experiment,
teaching, and by charitable, educational, public research, public safety/health,
environmental and government organizations.
Not permitted without a separate written commercial license:
- reselling, sublicensing or rebranding this software, or a modified copy of it
- running it as a hosted or paid service (SaaS, paid API, managed deployment)
- embedding it in a commercial product
- using it internally at a for-profit company in support of revenue-generating work
- monetising it with ads or subscriptions
For a commercial license, open an issue or contact
@abdurrehmandaudi.
What changed
LICENSE— the canonical PolyForm Noncommercial 1.0.0 text, verbatim
(verified against the licensor's published text), with the copyright line and
the requiredRequired Notice:line added- Every source file now carries a short copyright /
Required Noticeheader, so
the notice travels with copies of individual files too README.md— a License section spelling out what is and is not permitted
Copies taken before this release were not granted any license.
The proxy itself is identical to
v1.0.0
apart from the headers. Setup instructions for macOS/Linux and Windows are in
README.md.
v1.0.0 — ccproxy (macOS + Windows)
Run Claude Code against a relay that isn't a faithful Anthropic endpoint.
Tested against https://api.justwoker.icu (model claude-opus-4-8).
Your Claude Code points at http://127.0.0.1:8181 instead of at the relay. The
proxy translates both ways, repairs the model's JSON, runs web search itself,
and hands Claude Code a clean, standards-shaped stream.
What's fixed in this release
- Dropped tool calls. A strict
json.loads()failed on the model's literal
newlines and invalid escapes (warn\],grep -E "\d+"), soAgent,Monitor
and MCP calls were silently thrown away. A 4-step repair now recovers them:
strict → control chars → escape repair → bracket balancing. - Read / Write / Edit / Bash didn't work at all. They were described as text
instead of being sent in the payload'stoolsarray. The relay only
implements the four lowercase native names (read/write/edit/bash). - Every tool result was deleted before it went upstream, so the model never
saw what its tool returned and called the same tool again forever — the
"repeatsmkdiron every turn" loop. - Web search / web fetch are run by the proxy itself.
- 524 timeouts. A 1.6 MB, 1300-message history killed the relay. History is
now trimmed to a char budget and large tool output is truncated. - Streaming is synthesised locally, with a ping every 3 seconds.
Quick start — macOS / Linux
git clone https://github.com/abdurrehmandaudi/justdowork-proxy.git
cd justdowork-proxy
export UPSTREAM_API_KEY='sk-...'
sh start.shThen, in a second terminal:
sh run-claude.shQuick start — Windows
Install Python from https://www.python.org/downloads/ and tick
"Add python.exe to PATH" on the first installer screen.
git clone https://github.com/abdurrehmandaudi/justdowork-proxy.git
cd justdowork-proxy
setx UPSTREAM_API_KEY "sk-..."
:: setx only affects NEW windows -- close this one and open a new oneThen double-click start.bat (or run it), and in a second window:
run-claude.batNothing global is touched
run-claude.sh / run-claude.bat pass the proxy settings with --settings
for that one session. Your global ~/.claude/settings.json
(%USERPROFILE%\.claude\settings.json on Windows) is not modified, so your
other Claude Code setup keeps working side by side.
The first run creates a local .venv from requirements.txt — flask and
requests only, both pure Python. Nothing is installed globally.
Check it works
- Dashboard — http://127.0.0.1:8181/ — live token spend, tool calls,
dropped calls, retries, and where the data is going - Health — http://127.0.0.1:8181/health —
"key_set": truemeans your key was found - Tests —
sh run_tests.shorrun-tests.bat→ 70 pass, 0 fail, no API key needed
Requirements
- Python 3.9+
- An API key for the relay you point it at
- Claude Code
Full setup guide, troubleshooting and token-tuning notes are in
README.md.
agent_proxy.py is the previous version, kept for reference — use ccproxy.py.