best-effort token auto-refresh on expired token - #47
Conversation
When a request arrives and the OAuth token is already expired, the proxy now invokes `claude -p "ping"` to trigger a CLI-based token refresh before forwarding. This is a non-blocking convenience helper — if the refresh fails, the request proceeds with the expired token (API returns 401), same behavior as without this change. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Nicely done. I've still been doing it manually inside the 2 minute window to keep testing it but I have run into the issue of Claude logging me out. Now every 3-4 refreshes, so what seems to be once a day, when I use: claude -p "ping" --max-turns 1 --no-session-persistence I get: Not logged in · Please run /login This is another hurdle I can't get past. Anyone experience that or know how to stay logged in? |
Thanks! I haven't run into the logout issue on my end — my proxy has been refreshing successfully so far without getting logged out. Will keep an eye on it though. |
Ports 10 fixes discovered through code-level comparison with opencode-claude-auth (griffinmartin/opencode-claude-auth v1.4.10), verified with 38 unit tests and live-tested on an OpenClaw Max subscription. Critical fixes: - Add mcp_ prefix to all tool renames (Anthropic rejects bare PascalCase) - Compute real CCH hash (was hardcoded cch=00000) - Model-aware beta flags: exclude interleaved-thinking for Haiku, add effort only for 4.6 models, remove fake betas Reliability fixes: - Strip effort parameter for Haiku models (prevents 400 errors) - Direct OAuth token refresh via POST to claude.ai/v1/oauth/token with pre-expiry check, 401 retry, and race-condition dedup - Improved system prompt boundary detection (6 identity markers, generic Windows drive letter patterns, bounded to system[] array) Cleanup fixes: - Disable CC tool stubs by default (fixes zacdcook#43 tool-not-found loops) - Add orphaned tool_use/tool_result pair repair - Remove non-existent beta flags (advanced-tool-use, fast-mode) - Update Stainless SDK version to 0.90.0 Also adds test-functions.js with 38 unit tests covering all pure functions (no credentials or network required to run). Fixes zacdcook#41, zacdcook#43, zacdcook#46. Partially addresses zacdcook#47 (token refresh).
|
Day 2 update — auto-refresh triggered again successfully, zero downtime: 639 requests served before expiry, refreshed, #640 continued normally. |
|
I updated proxy.js with these PR changes but still no change on my end. |
Did you restart the proxy after updating? Also, this change likely won't work in Docker since there's no Claude installation inside the container. |
|
Sure did: Apr 16 09:18:10 clawdbot node[695175]: [token] Token expired, running claude CLI to refresh... Running it as a systemd service, not claude. Appreciate the response! |
- Replace hardcoded /home/ubuntu/.local/bin with findClaudeBin() that checks: config.json "claudeBin" → PATH → ~/.local/bin/claude - Log errors inside refreshOAuthToken() so failures are always visible - Remove duplicate error logging from call site Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Fixed — the previous version had a hardcoded path ( @yetdog Could you try pulling the latest changes and restarting? |
|
Roger that - thank you so much! Token set to expire in about 7 hours; will report back. |
|
Updated code verified — |
|
No go, sorry to say. Apr 19 07:17:51 clawdbot node[30887]: OpenClaw Billing Proxy v2.2.3 |
|
I have applied this PR to my local instance. It works flawlessly since two days (on Debian). |
|
@0n1cOn3 @yetdog Your first log (Apr 16) had |
|
Ubuntu 24
…Sent from my iPhone
On Apr 20, 2026, at 1:06 AM, Kaede ***@***.***> wrote:
[https://avatars.githubusercontent.com/u/13827054?s=20&v=4]hunandy14 left a comment (zacdcook/openclaw-billing-proxy#47)<#47?email_source=notifications&email_token=ABRHNARRF5S4HLJQLQ6ETVT4WW44FA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTIMRXHAZDKMZRGA3KM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJNLQOJPWG33NNVSW45C7N5YGK3S7MNWGSY3L#issuecomment-4278253106>
@0n1cOn3<https://github.com/0n1cOn3>
Awesome, thanks for confirming!
@yetdog<https://github.com/yetdog>
What OS are you on?
—
Reply to this email directly, view it on GitHub<#47?email_source=notifications&email_token=ABRHNARRF5S4HLJQLQ6ETVT4WW44FA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTIMRXHAZDKMZRGA3KM4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJNLQOJPWG33NNVSW45C7N5YGK3S7MNWGSY3L#issuecomment-4278253106>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/ABRHNASJFUM333T7ZVXSP2T4WW44FAVCNFSM6AAAAACXYW2TIGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DENZYGI2TGMJQGY>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
|
Some additional info. I don't actually have to login. If I get 401, and I just run "claude /login" from my open claw VM, it refreshes the token. Haven't tried it w/o the /login. I wonder if the original suggested cron job would work now? |
Works also without /login. Already tested that :) |
Token has been refreshed. 8.0h is the standard. The log shows that your token has been refreshed 10mins after you've ran Or what you wanna tell us? :D |
This PR attempts to obtain the token via the Claude CLI: I'm unsure if Claude requires a persistent login state. Another solution is to try obtaining the token directly via the API instead of the CLI, Also, could you print your claude's location? |
Short update: still rocking solid on debian so far. Since then, no further intervention has become necessary. :D |
Well, I had just recently manually run 'claude' for it to refresh. It's still not working automatically for me. which claude: |
Hmm.. which OS do you use and have you applied this PR to the project and restarted the proxy after that? I have set it up once - and since then, works flawlessly. Running under Debian 13 here. |
Summary
When a request arrives and the OAuth token is expiring (<60s remaining),
the proxy runs
claude -p "ping" --max-turns 1 --no-session-persistenceto refresh the token before forwarding.
Best-effort only — if refresh fails, the request proceeds as if this
code did not exist. Triggered on-demand, not by cron or polling.
Why 60 seconds?
Claude CLI only rotates the token when <2 minutes remain
(confirmed by @DBostik in #32).
60 seconds sits inside this window with enough buffer for the ~8s CLI invocation.
Design decisions
Refresh only happens when a real request needs it.
the original
getToken()flow is untouched.refreshInProgressensures concurrent requests sharea single CLI invocation instead of spawning many.
claude -p "ping"rather thancalling the OAuth endpoint directly. The CLI already handles token
refresh reliably, so reusing it avoids breakage if Anthropic changes
the OAuth flow in the future.
() =>→async () =>).Original request handling logic is not modified.
Verified in production
Proxy ran for ~7 hours, served 78 requests. Token expired naturally,
auto-refreshed in 8 seconds, and continued serving — zero downtime:
Limitations
claudeCLI available on PATH. Only tested with systemd deployment, not Docker.Relates to #6, #39