Skip to content

Releases: abdurrehmandaudi/justdowork-proxy

v3.0.0 — remembers what matters, sees the whole file, stops wasting seconds

Choose a tag to compare

@abdurrehmandaudi abdurrehmandaudi released this 07 Oct 17:01

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'
claude

What 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 no cache_control
    at all: cache_write = 15159 against in_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 strict json.loads
    failed and the whole tool call vanished. Monitor was dropped 3049 times
    in one debug_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/Bash are mapped to the relay's
    lowercase native names (read/write/edit/bash) and sent in the payload's
    tools array. Everything else uses the <tool_call> text protocol.
  • Every tool result was being deleted — _is_empty_content() looked for a
    text field, but a tool_result carries its payload in content. So every
    tool result looked empty and was dropped, and the model called the same tool
    again forever. tool_use, tool_result, image, document and
    search_result now 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.sh

If you would rather do it yourself:

python3 -m venv .venv
.venv/bin/python3 -m pip install -r requirements.txt

If python3 is missing, install it with Homebrew:

brew install python

Windows

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.bat

If you would rather do it yourself:

python -m venv .venv
.venv\Scripts\python.exe -m pip install -r requirements.txt

...

Read more

v1.0.1 — PolyForm Noncommercial license

Choose a tag to compare

@abdurrehmandaudi abdurrehmandaudi released this 06 Oct 15:01

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 required Required Notice: line added
  • Every source file now carries a short copyright / Required Notice header, 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)

Choose a tag to compare

@abdurrehmandaudi abdurrehmandaudi released this 06 Oct 14:49

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+"), so Agent, 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's tools array. 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
    "repeats mkdir on 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.sh

Then, in a second terminal:

sh run-claude.sh

Quick 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 one

Then double-click start.bat (or run it), and in a second window:

run-claude.bat

Nothing 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": true means your key was found
  • Tests — sh run_tests.sh or run-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.