Skip to content

fix(oauth): include client_id, scope, and rotating refresh token in token refresh#1724

Closed
Saxin wants to merge 19 commits intoqwibitai:mainfrom
Saxin:main
Closed

fix(oauth): include client_id, scope, and rotating refresh token in token refresh#1724
Saxin wants to merge 19 commits intoqwibitai:mainfrom
Saxin:main

Conversation

@Saxin
Copy link
Copy Markdown

@Saxin Saxin commented Apr 10, 2026

Summary

  • The platform.claude.com/v1/oauth/token endpoint requires client_id and scope fields in the POST body — without them it returns 400 Invalid request format
  • Without a successful refresh the access token expired silently, OneCLI served the stale token, and agent containers received 401 Invalid authentication credentials
  • Also persists the rotating refresh_token from the response back to credentials.json so subsequent refreshes don't use a revoked token

Changes

src/oauth-token.ts only:

  • Added CLAUDE_CLIENT_ID and CLAUDE_SCOPES constants
  • Added client_id and scope to the callRefreshEndpoint POST body
  • callRefreshEndpoint now returns and propagates the new refreshToken
  • ensureFreshOAuthToken writes the updated refreshToken back to credentials.json

Test plan

  • Verified on live instance — bot has been running since 2026-04-09 with no 401 errors
  • Confirm oauth-token.test.ts passes (if tests exist for this module)

🤖 Generated with Claude Code

gavrielc and others added 19 commits April 6, 2026 21:46
Extract message_thread_id from incoming messages and pass it back when
replying so responses go to the correct topic instead of General chat.

Closes qwibitai#1420

Co-Authored-By: flobo3 <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
When users send photos, videos, voice messages, audio, or documents via
Telegram, download them to the group's attachments/ directory so the
container agent can read them. Falls back to placeholder text if the
download fails. Stickers, locations, and contacts remain as placeholders.

Files are saved to groups/<name>/attachments/ on the host, accessible at
/workspace/group/attachments/ inside the container.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Add reply_to_message_id, reply_to_message_content, reply_to_sender_name
  columns to messages table with idempotent migration
- Update storeMessage to write reply fields
- Update getMessagesSince and getNewMessages to SELECT reply fields
- Fix replyToSenderName fallback to 'Unknown' for anonymous senders
- Add DB tests: store/retrieve reply context, null for non-replies,
  round-trip through getNewMessages
- Add formatting tests: quoted_message XML rendering, escaping,
  missing content edge case
- Add Telegram tests: reply extraction, caption fallback, anonymous
  sender fallback, no-reply case

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Voice messages sent to Telegram are now downloaded and transcribed
using OpenAI's Whisper API, delivered to the agent as [Voice: <text>].
Falls back to the file path when no API key is set, and to an error
message if transcription throws.

Also implements the previously-tested file download feature for all
Telegram media types (photo, video, audio, document), and restores
the grammy dependency that was dropped from package.json.

New file: src/transcription.ts — channel-agnostic buffer transcription
module, reusable across WhatsApp and Telegram.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Replaces the OpenAI Whisper API with a local whisper.cpp binary.
No API key or network required — runs entirely on-device.

Audio is converted to 16kHz mono WAV via ffmpeg, then passed to
whisper-cli. WHISPER_BIN and WHISPER_MODEL env vars can override
the binary path and model file.

The transcribeAudioBuffer(buffer, filename) signature is unchanged,
so both Telegram and any future WhatsApp integration work without
modification.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Switched to local whisper.cpp for transcription — openai package is no
longer needed.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Remove "WhatsApp only" restriction — works for Telegram and WhatsApp
- Replace git-merge-based install with inline code (the whatsapp skill
  branch had a WhatsApp-specific transcription.ts; ours is generic)
- Add Linux build instructions (apt + cmake + build from source)
- Document WHISPER_BIN/.env fallback for restricted PATH environments
- Consistent with transcribeAudioBuffer(buffer, filename) API

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…arity

- Note Telegram is now supported (not WhatsApp-only)
- Phase 2 code changes are WhatsApp-only — Telegram uses transcription.ts directly
- Add pointer to use-local-whisper as a no-cost alternative
- Add Linux restart command alongside macOS launchctl

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Adds background 30-minute interval in index.ts to keep the OAuth token
fresh regardless of activity. Previously ensureFreshOAuthToken was only
called on container launch, so long idle periods caused 401s on the next
message.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Downloads voice messages and transcribes them using whisper-cli + ffmpeg.
Falls back to file path on null result, and to error message on failure.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…stemd start script

- Pre-install mcp-remote in container image to reduce cold start time
- Wire ha-mcp server into agent-runner via mcp-remote pointing at localhost:9583
- Add ha-direct-control skill for 1-2 call HA requests without tool discovery
- Rewrite start-nanoclaw.sh to delegate to systemctl (prevents duplicate instances)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…oken refresh

The platform.claude.com/v1/oauth/token endpoint requires client_id and
scope fields or it returns 400 Invalid request format. Without a successful
refresh the token expired silently, OneCLI served the stale token, and
containers got 401 Invalid authentication credentials.

Also: persist the rotating refresh_token from the response back to
credentials.json so subsequent refreshes don't use a revoked token.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@Saxin
Copy link
Copy Markdown
Author

Saxin commented Apr 10, 2026

Closing — branch contained unrelated commits from fork. Reopening with a clean single-feature branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants