opencode-claude-spoof
v1.0.3
Published
Complete Claude Code session spoofing plugin for OpenCode
Readme
OpenCode Claude Spoof Plugin v2.0
A comprehensive plugin that makes OpenCode sessions indistinguishable from official Claude Code sessions by matching tool naming patterns, headers, and request signatures.
The Problem
As of January 9, 2026, Anthropic added server-side detection that blocks OAuth tokens from being used with non-Claude-Code clients. The error:
This credential is only authorized for use with Claude Code and cannot be used for other API requests.Detection Mechanism Discovered
Through investigation, we found Anthropic detects non-Claude-Code clients by checking tool names in the request body:
- Claude Code uses:
PascalCase_toolpattern (e.g.,Bash_tool,Read_tool,Question_tool) - OpenCode uses: lowercase names (e.g.,
bash,read,question)
The server validates tool names and rejects requests with non-matching patterns.
The Solution
This plugin intercepts all Anthropic API requests and:
- Transforms outgoing tool names to
PascalCase_toolformat - Transforms incoming tool names back to original format
- Sets correct headers (User-Agent, anthropic-beta, etc.)
- Strips OpenCode-identifying headers (x-opencode-*)
- Adds
?beta=trueto the messages endpoint URL
Tool Name Transformation
| OpenCode Tool | Transformed Name |
|---------------|------------------|
| bash | Bash_tool |
| read | Read_tool |
| write | Write_tool |
| edit | Edit_tool |
| glob | Glob_tool |
| grep | Grep_tool |
| question | Question_tool |
| task | Task_tool |
| webfetch | Webfetch_tool |
| todowrite | Todowrite_tool |
| python-repl | PythonRepl_tool |
| mcp__sqlite__read_query | McpSqliteReadQuery_tool |
Responses are transformed back to original tool names automatically.
Quick Start: Testing & Validation
1. Check Version Compatibility (First!)
cd opencode-claude-spoof
bun tests/check-updates.mjsExpected output:
Installed Claude CLI: 2.1.2
Plugin CONFIG.VERSION: 2.1.2
✓ VERSIONS MATCH - Plugin is up to dateIf versions don't match: Update CONFIG.VERSION in index.mjs to match installed Claude CLI version.
2. Run Quick Validation (No API Calls)
bun tests/run-all.mjs --quickThis runs:
- 62 unit tests for tool transformations
- 17 config validation tests
Expected: All tests pass.
3. Run Full Validation (With API Calls)
# Enable debug mode first
sed -i '' 's/DEBUG: false/DEBUG: true/' index.mjs
# Run full test suite
bun tests/run-all.mjs
# Check debug log
cat /tmp/opencode-claude-spoof-debug.logExpected: All tests pass, debug log shows Response status: 200 OK.
4. Manual Smoke Test
rm -f /tmp/opencode-claude-spoof-debug.log
OPENCODE_DISABLE_DEFAULT_PLUGINS=true opencode run "What is 2+2? Reply with just the number." -m anthropic/claude-sonnet-4-20250514Expected: Response is 4, debug log shows Response status: 200 OK.
Installation
Option 1: From npm (Recommended)
// ~/.config/opencode/opencode.jsonc
{
"plugin": [
"opencode-claude-spoof"
]
}Option 2: From Local Clone
# Clone the repo
git clone https://github.com/huynle/opencode-claude-spoof.git
cd opencode-claude-spoof
bun installThen add to ~/.config/opencode/opencode.jsonc:
{
"plugin": [
"file:///path/to/opencode-claude-spoof"
]
}Note: GitHub URLs (github:user/repo) don't work due to opencode appending @latest which GitHub doesn't support. Use npm or local file path instead.
With Default Plugins Disabled
OPENCODE_DISABLE_DEFAULT_PLUGINS=true opencodeConfiguration
Edit CONFIG object in index.mjs:
const CONFIG = {
CLIENT_ID: "9d1c250a-e61b-44d9-88ed-5944d1962f5e", // Official Claude Code OAuth Client ID
VERSION: "2.1.2", // Claude Code version to spoof
BETA_FEATURES: [
"oauth-2025-04-20",
"interleaved-thinking-2025-05-14",
],
DEBUG: false, // Set to true for verbose logging
};Debug Mode
Enable Debug Logging
// In index.mjs, change:
DEBUG: false
// To:
DEBUG: trueOr via sed:
sed -i '' 's/DEBUG: false/DEBUG: true/' index.mjsDebug Log Location
All debug output is written to: /tmp/opencode-claude-spoof-debug.log
Debug Log Format
[SPOOF INIT 2026-01-09T15:10:34.625Z] Plugin initializing, client=object
[SPOOF] OAuth auth detected, setting up spoof fetch
[SPOOF] Applied spoof headers, User-Agent: claude-cli/2.1.2 (external, cli)
[SPOOF] Final headers being sent:
[SPOOF] accept: application/json
[SPOOF] anthropic-beta: oauth-2025-04-20,interleaved-thinking-2025-05-14
[SPOOF] anthropic-version: 2023-06-01
[SPOOF] authorization: Bearer [REDACTED]
[SPOOF] content-type: application/json
[SPOOF] user-agent: claude-cli/2.1.2 (external, cli)
[SPOOF] ORIGINAL REQUEST BODY (first 5 tools):
[SPOOF] [0] question
[SPOOF] [1] bash
[SPOOF] [2] read
[SPOOF] [3] write
[SPOOF] [4] edit
[SPOOF] TRANSFORMED REQUEST BODY (first 5 tools):
[SPOOF] [0] Question_tool
[SPOOF] [1] Bash_tool
[SPOOF] [2] Read_tool
[SPOOF] [3] Write_tool
[SPOOF] [4] Edit_tool
[SPOOF] Transformed tools (96): Question_tool, Bash_tool, ...
[SPOOF] Added ?beta=true to URL: https://api.anthropic.com/v1/messages?beta=true
[SPOOF] Making spoofed request to: https://api.anthropic.com/v1/messages?beta=true
[SPOOF] Response status: 200 OK
[SPOOF] RESPONSE TRANSFORM: "Bash_tool" -> "bash"Key Debug Lines to Check
| Log Line | Meaning |
|----------|---------|
| Plugin initializing | Plugin loaded successfully |
| OAuth auth detected | OAuth token found and being used |
| Applied spoof headers | Headers being modified |
| Transformed tools (96) | All tools being transformed |
| Response status: 200 OK | API accepted request |
| RESPONSE TRANSFORM | Response tool names being converted back |
Troubleshooting
Error: "This credential is only authorized for use with Claude Code"
This means Anthropic detected the request as non-Claude-Code. Debug steps:
Enable debug mode:
sed -i '' 's/DEBUG: false/DEBUG: true/' index.mjsClear and run test:
rm -f /tmp/opencode-claude-spoof-debug.log OPENCODE_DISABLE_DEFAULT_PLUGINS=true opencode run "test" -m anthropic/claude-sonnet-4-20250514Check the debug log:
cat /tmp/opencode-claude-spoof-debug.logVerify these are present:
Transformed tools (XX):withPascalCase_toolnamesuser-agent: claude-cli/X.X.X (external, cli)anthropic-beta: oauth-2025-04-20,...?beta=truein URL
Check for error response:
grep -A5 "Error response body" /tmp/opencode-claude-spoof-debug.log
Error: Tool not recognized by OpenCode
The response transformation regex may not be matching. Check:
grep "RESPONSE TRANSFORM" /tmp/opencode-claude-spoof-debug.logIf no transforms are logged, the regex pattern needs updating. See "Fixing Response Transform" below.
Plugin not loading
Check plugin path:
cat ~/.config/opencode/opencode.jsonc | grep -i spoofCheck for syntax errors:
bun index.mjsVerify dependencies:
bun installCheck init log:
grep "SPOOF INIT" /tmp/opencode-claude-spoof-debug.log
Claude CLI Version Mismatch
bun tests/check-updates.mjsIf mismatch is detected, update CONFIG.VERSION in index.mjs.
Fixing When Detection Changes
If Anthropic updates their detection and the plugin stops working, follow this workflow:
Step 1: Identify the Failure
# Enable debug
sed -i '' 's/DEBUG: false/DEBUG: true/' index.mjs
# Clear log and test
rm -f /tmp/opencode-claude-spoof-debug.log
OPENCODE_DISABLE_DEFAULT_PLUGINS=true opencode run "test" -m anthropic/claude-sonnet-4-20250514
# Check error
cat /tmp/opencode-claude-spoof-debug.log | tail -50Look for:
- HTTP status code (403, 401, 400?)
- Error message in response body
- What's different from expected headers/body
Step 2: Compare with Real Claude Code
Capture real Claude Code requests using mitmproxy:
# Install mitmproxy
brew install mitmproxy
# Start proxy
mitmproxy --mode regular -p 8080
# In another terminal, run Claude Code through proxy
HTTPS_PROXY=http://localhost:8080 claude "test"Compare:
- Headers (User-Agent, anthropic-beta, etc.)
- Request body structure
- Tool names pattern
- URL parameters
Step 3: Check Claude CLI Version
claude --versionIf version changed, update in index.mjs:
VERSION: "X.X.X", // Update to match claude --versionStep 4: Check for New Headers
Look at mitmproxy output for any new headers Claude Code sends:
# Common headers to check
grep -i "user-agent\|anthropic\|x-claude\|x-request" mitmproxy-logAdd any new required headers in applySpoofHeaders() function.
Step 5: Check for New Beta Features
# In mitmproxy, check anthropic-beta header
grep "anthropic-beta" mitmproxy-logUpdate BETA_FEATURES array if new features are required:
BETA_FEATURES: [
"oauth-2025-04-20",
"interleaved-thinking-2025-05-14",
// Add new features here
],Step 6: Check Tool Name Pattern
If tool name pattern changed:
- Look at how tools are named in Claude Code requests
- Update
toClaueCodeToolName()function - Update
fromClaudeCodeToolName()function - Update response transform regex
Step 7: Validate the Fix
# Run unit tests
bun test tests/unit.test.mjs
# Run config tests
bun test tests/config.test.mjs
# Run full validation
bun tests/run-all.mjsKey Code Sections to Modify
| Section | Location | What to Change |
|---------|----------|----------------|
| Version | index.mjs:28 | VERSION: "X.X.X" |
| Beta Features | index.mjs:36-41 | BETA_FEATURES: [...] |
| User-Agent | index.mjs:73 | SPOOF_USER_AGENT |
| Headers | index.mjs:200-229 | applySpoofHeaders() |
| Tool Transform | index.mjs:278-288 | toClaueCodeToolName() |
| Tool Reverse | index.mjs:296-307 | fromClaudeCodeToolName() |
| Response Regex | index.mjs:523 | The regex pattern |
Fixing Request Tool Transform
Location: index.mjs:278-288
function toClaueCodeToolName(name) {
if (!name) return name;
const pascalCase = name
.split(/[-_\s]+/)
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join('');
return `${pascalCase}_tool`;
}If pattern changes: Update the transformation logic to match new pattern.
Fixing Response Transform
Location: index.mjs:523
text = text.replace(/"name"\s*:\s*"([A-Z][a-zA-Z]*)_tool"/g, (match, toolName) => {
const original = fromClaudeCodeToolName(toolName + '_tool');
return `"name": "${original}"`;
});If pattern changes: Update the regex to match new response format.
Test Suite Reference
Quick Check (No API Calls)
# Check if Claude CLI has updated
bun tests/check-updates.mjs
# Run unit tests only (62 tests)
bun test tests/unit.test.mjs
# Run config validation (17 tests)
bun test tests/config.test.mjs
# Run all quick tests
bun tests/run-all.mjs --quickFull Validation (With API Calls)
# Run everything including live API tests
bun tests/run-all.mjs
# Or run integration tests directly
bun tests/integration.test.mjsWhat the Tests Verify
| Test File | Tests | What It Checks |
|-----------|-------|----------------|
| unit.test.mjs | 62 | Tool name transformations, regex patterns, edge cases |
| config.test.mjs | 17 | Plugin config, Claude CLI version, auth tokens, OpenCode config |
| integration.test.mjs | 10 | Live API calls, headers, responses, tool execution |
| check-updates.mjs | - | Detects if Claude CLI updated (need to update plugin) |
Adding New Tests
If you fix a new detection mechanism, add tests:
- Unit test: Add to
tests/unit.test.mjs - Integration test: Add to
tests/integration.test.mjs - Run to verify:
bun test tests/unit.test.mjs
Headers Reference
Headers Sent
user-agent: claude-cli/2.1.2 (external, cli)
anthropic-beta: oauth-2025-04-20,interleaved-thinking-2025-05-14
anthropic-version: 2023-06-01
authorization: Bearer <oauth_token>
content-type: application/json
accept: application/jsonNote: The (external, cli) suffix in User-Agent is required.
Headers Stripped
x-opencode-projectx-opencode-sessionx-opencode-requestx-opencode-clientx-opencode-directoryx-api-key
Validation Results
Last Validated: January 9, 2026
Status: FULLY WORKING
Test Results Summary
| Test Category | Details | Status | |--------------|---------|--------| | Standard Tools | bash, read, write, edit, glob, grep | PASS | | Hyphenated Tools | python-repl, web-search, ask-user-question | PASS | | MCP Tools | mcp__sqlite__, mcp__filesystem__, Firecrawl, Context7 | PASS | | Round-trip Transform | OpenCode -> Claude -> OpenCode | PASS | | Request Body Transform | 96 tools transformed correctly | PASS | | Response Stream Transform | Streaming SSE tool names converted back | PASS | | Multi-tool Calls | 3 tools in parallel (bash, read, glob) | PASS | | Task Tool (Sub-agents) | Parent spawns child agent with own tool set | PASS | | API Response | All requests return 200 OK | PASS |
What We Tried That Didn't Work
| Approach | Result |
|----------|--------|
| Changing User-Agent to claude-code/2.1.2 | Still detected |
| Using oc_ prefix on tools | Still detected |
| Removing claude-code-20250219 beta flag | Still detected |
| Various header combinations alone | Tool names were the key |
File Structure
opencode-claude-spoof/
├── .artifacts/ # Test outputs and debug logs (git-ignored)
│ ├── .gitkeep # Keeps directory in git
│ ├── debug.log # Plugin debug output
│ └── *.json # Test results
├── .gitignore # Git ignore rules
├── index.mjs # Main plugin code
├── deep-validate.mjs # Quick validation script (9 test suites)
├── package.json # Dependencies
├── bun.lock # Lock file
├── README.md # This documentation
├── REPORT.md # Deep validation report with evidence
└── tests/
├── run-all.mjs # Master test runner
├── unit.test.mjs # 62 unit tests (bun:test)
├── config.test.mjs # 17 config validation tests
├── integration.test.mjs # Live API integration tests
└── check-updates.mjs # Version mismatch detectorPotential Future Detection Vectors
Anthropic may add more detection. Watch for:
- Request timing patterns - May need to add delays
- Session behavior - May need to match Claude Code's session patterns
- Additional headers - Monitor Claude Code updates for new headers
- Token metadata - Server may validate token metadata more strictly
- Tool schemas - May validate tool input schemas match expected patterns
- Request fingerprinting - TLS fingerprint, connection patterns
Related Resources
- Original plugin repo: https://github.com/anomalyco/opencode-anthropic-auth
- Issue #12: "The auth no longer works" (opened Jan 9, 2026)
- PR #13: "feat: multi-layered bypass with automatic fallback"
User Config Locations
- Config file:
$HOME/.config/opencode/opencode.jsonc - Auth tokens:
~/.local/share/opencode/auth.json
Disclaimer
This plugin is for educational and research purposes. Use responsibly and in accordance with Anthropic's terms of service.
License
MIT
