Integrated into release/v3.8.0 — three fixes for CliProxyApi: Anthropic-shape body routing to /v1/messages, Capy premium extras strip, and mcp_* tool name rewrite to avoid Anthropic gate. Tests added covering all three categories.
Integrated into release/v3.8.0 — adds target-based metadata aggregation for combo entries in /v1/models using least-common-denominator approach (context_length, max_output_tokens, capabilities, modalities).
Integrated into release/v3.8.0 — degrades background:true to synchronous execution instead of 400, enabling Capy and similar clients that set background:true by default to work seamlessly.
Integrated into release/v3.8.0 — adds sanitizeReasoningEffortForProvider hook to BaseExecutor, fixing xhigh→high downgrade for non-supporting providers and full strip for mistral/devstral and GitHub Claude models.
Integrated into release/v3.8.0 — allows optional-key providers (SearXNG, Petals, self-hosted chat, OpenAI/Anthropic-compatible) to pass connection test by centralizing the check in providerAllowsOptionalApiKey().
Integrated into release/v3.8.0 — removes .ts import extensions, replaces as any casts with proper types, and normalizes provider alias resolution in combo context_length calculation.
Integrate the Ollama hosted web search API (ollama.com/api/web_search) as a
new search provider, bringing the total to 11.
What:
- Register "ollama-search" in the search provider registry with POST to
ollama.com/api/web_search, Bearer auth, web-only, max 10 results
- Add credential fallback from ollama-cloud (same API key)
- Add request builder (query + max_results) and response normalizer
(maps {results: [{title, url, content}]} to SearchResult[] with
optional chaining and full_text content mapping)
- Wire into Zod validation, provider constants, and connection test config
- Add 4 integration tests through handleSearch covering builder, normalizer,
empty results, and missing results field edge cases
- Update registry and route listing tests (provider count 10→11)
Why:
Gives OmniRoute users another search backend option, reusing their existing
Ollama Cloud API key with no additional setup.
Testing:
- 4 new integration tests in search-handler-extended.test.ts using handleSearch
with mocked fetch
- 2 new assertions + count update in search-registry.test.ts
- Updated provider listing test in search-route.test.ts
- All 60 tests pass, no new ESLint or TypeScript errors
Centralize the requiresApiKey guard into providerAllowsOptionalApiKey()
in src/shared/constants/providers.ts so testApiKeyConnection and
validateProviderApiKey share the same logic. The test route was missing
openai-compatible-* and anthropic-compatible-* checks, causing connection
test failures for those provider types when no API key was provided.
Test file now imports providerAllowsOptionalApiKey and
SELF_HOSTED_CHAT_PROVIDER_IDS directly from the source instead of
duplicating them.
Follow-up after testing the Capy BYOK flow. The unconditional
`delete transformed.thinking` was killing legitimate thinking configs
that applyThinkingBudget had already converted to Anthropic-valid form
({type:"enabled"|"disabled", budget_tokens:N}). Replace with a
conditional strip that:
- Preserves Anthropic-valid shapes (enabled/disabled + numeric
budget_tokens, no extra fields)
- Strips Capy/Anthropic-SDK shapes Anthropic doesn't accept
(type:"adaptive", or presence of the Capy-specific `display` field)
Symptom we observed: clients sending
`thinking: {type:"adaptive", display:"summarized"}` on /v1/messages
saw plain text responses with no thinking block. Their UIs (e.g. Capy)
fell back to rendering answer text inside the empty "Thought" section.
With applyThinkingBudget in adaptive mode (default for many user
configs), the body reaching this executor already has a valid
Anthropic shape — the unconditional strip was undoing that work.
Bodies that bypass applyThinkingBudget (passthrough mode) still get
stripped here because Anthropic 400s on the unconverted shape.
5 new test cases cover the preserve/strip matrix on Anthropic-shape
and OpenAI-shape bodies.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## OAuth (browser PKCE)
- **`poll-callback`**: generalise from codex-only to all
`PKCE_CALLBACK_PROVIDERS` (codex, windsurf, devin-cli); state slot
is now dynamic (`__codexCallbackState` vs `__windsurfCallbackState`)
- **`OAuthModal`**: replace the codex-specific callback-server block
with a shared `PKCE_CALLBACK_SERVER_PROVIDERS` branch covering all
three providers; same start-callback-server + poll-callback polling
loop, same 2-second interval, same 5-minute timeout
- **`OAuthModal` redirect URI fallback**: windsurf/devin-cli now use
`http://localhost:{port}/auth/callback` (correct path) instead of
the generic `/callback` on non-true-localhost
- **`/auth/callback` page**: new Next.js route (`src/app/auth/callback`)
that re-exports `/callback/page`; Windsurf redirects to `/auth/callback`
so the popup auto-completes without manual URL paste (postMessage +
BroadcastChannel + localStorage)
## API-token (WINDSURF_API_KEY / paste-token)
- **`route.ts` — `import-token` action**: new POST action for
`IMPORT_TOKEN_PROVIDERS` (windsurf, devin-cli); calls
`provider.mapTokens({ accessToken: token })` skipping the HTTP
exchange and creating the connection directly
- **`oauthImportTokenSchema`**: Zod schema `{ token, connectionId? }`
- **`OAuthModal` — "Paste API Key" tab**: tab-switcher UI for
windsurf/devin-cli; sends `POST /import-token`; errors shown inline
## Security: remove hardcoded Firebase API key (GH secret alert)
- `AIzaSyBpLTEGSt59AUPKxBb7lIWjSE2ZXQH7mgU` removed from both
`oauth.ts` and `tokenRefresh.ts`; code now reads only
`process.env.WINDSURF_FIREBASE_API_KEY`
- Added `WINDSURF_FIREBASE_API_KEY` to `.env.example` with the
public key value and an explanation that it is a client-side
credential embedded in the Windsurf app (not a secret)
- `tokenRefresh.ts`: graceful `return null` with warn log when
key is absent (import tokens are long-lived and skip refresh anyway)
## Docs
- Sync 40 i18n CHANGELOG mirrors to v3.8.0 root content
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Update Pollinations request transformation to send the selected model
and stream flag so requests match the active endpoint behavior.
Align the ChatGPT TLS client with shared proxy resolution so dashboard
proxy context is honored before falling back to environment settings.
Also refresh provider display names across dashboard pages, correct the
Claude extra-usage toggle messaging and visual state, and mark
Pollinations as offering a free public endpoint.
Adds two providers for Windsurf/Devin CLI access:
**`devin-cli`** — official path via ACP JSON-RPC over stdio
- Spawns `devin acp --agent-type summarizer` as a subprocess
- Full streaming via session/update notifications
- Auth: WINDSURF_API_KEY env var or `devin auth login` stored creds
- Binary auto-discovered: %LOCALAPPDATA%\devin\cli\bin\devin.exe (Win),
~/.local/share/devin/bin/devin (Linux), PATH fallback
- Model IDs verified from model_configs_v2.bin in Devin CLI binary
(swe-1.6-fast, claude-opus-4.7-max, gpt-5.5-high, gemini-3.1-pro-high, etc.)
**`windsurf`** — direct gRPC-web fallback (no binary needed)
- Calls server.self-serve.windsurf.com directly via gRPC-web+proto
- Auth: token from windsurf.com/show-auth-token
- MODEL_ALIAS_MAP dot-to-dash normalisation for all 100+ catalog models
Supporting changes:
- cliRuntime.ts: add `devin` to CLI_TOOLS with Windows/Linux binary paths
- tokenRefresh.ts: Firebase STS refresh for devin-cli + windsurf
- oauth providers: windsurf + devin-cli (token import / device-code)
- providerRegistry: full model list from CLI binary (GPT-5.5, GPT-5.4,
GPT-5.3-Codex, Claude Opus 4.7, SWE-1.6, DeepSeek V4, Kimi K2.6, etc.)
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Per gemini-code-assist review on #2165:
1. Extra strip-list fields (PR description claimed but implementation
missed):
- client_info, prompt_cache_key, safety_identifier, metadata
These trigger Anthropic's "Extra usage required" 400 the same way
output_config does on CPA's /v1/messages surface.
2. applyMcpToolNameRewrite no longer mutates the nested input body. The
shallow clone produced by transformRequest's `{...body}` left nested
tools/messages/tool_choice as shared references with the original
body, so direct `t.name = rewritten` assignments leaked back to the
caller. Now returns a new array for tools (cloning the rewritten
entries), a new messages array with cloned content blocks for each
message containing a rewritten tool_use, and a cloned tool_choice
object when rewrite is needed. Unchanged elements share references
to keep memory churn minimal.
3. isAnthropicShape now treats any top-level `system` field as a strong
signal — including the string form (Anthropic supports both `system:
"Rules"` and `system: [{type: "text", text: "Rules"}]`). Previously
only the array form was recognized, so plain-string Anthropic-shape
bodies were routed to /v1/chat/completions and returned OpenAI SSE
that Anthropic SDK clients can't decode.
On tool_result tool_use_id rewriting: tool_use_id is the opaque id
field of the corresponding tool_use block (e.g. "toolu_01abc"), not
the tool name. Anthropic's ^mcp_[^_] gate fires on `name`, not `id`,
so ids do not need rewriting. The PR description claim to rewrite
tool_use_id refs was overspecified relative to the actual constraint.
Per gemini-code-assist review on #2164:
1. Re-introduce the BACKGROUND_DEGRADE warning log (mentioned in PR
description but missing from code). Operators can grep for this to
identify clients still requesting background mode that should be
reconfigured.
2. Make the log conditional on `background === true` (not on the strip
itself). background:false / unset now passes through silently with
no log spam.
3. Add unit test for both background unset AND background:false cases
asserting no BACKGROUND_DEGRADE log is emitted, and update the
existing background:true test to assert the log IS emitted.
Per gemini-code-assist review on #2163, the registry contextLength/
maxOutputTokens values were being silently capped to 8192 by
capMaxOutputTokens() because the model IDs were absent from MODEL_SPECS,
falling back to __default__.maxOutputTokens. Adds entries with
matching limits :
- claude-opus-4-5-20251101 : 64000 max_out (overrides prefix-match to
claude-opus-4-5 which has 32768)
- claude-opus-4-6 : 128000 max_out, 1M context tier
- claude-sonnet-4-6, claude-sonnet-4-5-20250929, claude-haiku-4-5-20251001
: 64000 max_out each
- kimi-k2.6 : 262144 max_out, 262144 context, with aliases for
kimi-k2.6-thinking and kimi-for-coding
- mimo-v2.5-pro / mimo-v2.5 : 131072 max_out, 1048576 context
- mimo-v2-omni : 131072 max_out, 262144 context
- mimo-v2-flash : 65536 max_out, 262144 context
Each entry carries `aliases` where dot-notation variants exist
(e.g. claude-opus-4.6), so capMaxOutputTokens resolves them via the
explicit alias lookup before falling back to the prefix-match path.
Per gemini-code-assist review on #2162:
- Add `!Array.isArray(b.reasoning)` guard so spread of array-typed
reasoning doesn't pollute the body with numeric-keyed entries
- Hoist /devstral/i and /(claude|haiku|oswe)/i to module-level
MISTRAL_NO_REASONING_EFFORT_PATTERN and
GITHUB_NO_REASONING_EFFORT_PATTERN constants to avoid per-call
RegExp construction
Anthropic Messages API silently gates client-declared tool names matching
^mcp_[^_].* behind their "Extra usage required" / "out of extra usage" 400
error — the prefix is reserved for their server-side MCP connector tools.
The error is misleading: it looks like a quota exhaustion, but it's body-
shape gating triggered purely by the regex match on `tools[].name`.
Bisected character-by-character against the real Anthropic API via CPA
(uTLS spoof, Claude OAuth account):
Gate hit (HTTP 400): mcp_call, mcp_query, mcp_x, mcp_test, mcp_anything
Bypass (HTTP 200): Mcp_call, MCP_call, _mcp_call, xmcp_call,
mcp__call, mcp-call, mcpcall, my_mcp_call
Independent of system prompt, metadata.user_id shape, thinking/output_config
presence, request size, or tool count. Capy declares mcp_call + mcp_query
for its MCP bridge, so every Capy Claude-BYOK request 400'd.
Fix: in CliproxyapiExecutor.transformRequest, for Anthropic-shape bodies,
rewrite tool names matching ^mcp_[^_] to "M" + name.slice(1) (mcp_call →
Mcp_call). Same rewrite applied to:
- tools[].name
- messages[].content[type=tool_use].name (for multi-turn)
- tool_choice.name (when type=tool)
Build a reverse map {rewritten → original} and attach as body._toolNameMap.
chatCore.ts:mergeResponseToolNameMap already reads this from finalBody and
forwards it to the SSE passthrough stream
(utils/stream.ts:restoreClaudePassthroughToolUseName), which rewrites
tool_use.name back to the client's original namespace on response chunks.
End-to-end: Capy sends mcp_call → CPA/Anthropic sees Mcp_call → response
tool_use blocks emit Mcp_call → client receives mcp_call. Capy's dispatch
keyed on mcp_call works unchanged.
_toolNameMap is filtered out of the JSON.stringify body sent to CPA so the
in-memory channel doesn't leak over the wire.
When Capy (Anthropic SDK 0.90.0) BYOK Pro forwards a request via OmniRoute,
it injects:
- thinking: { type: "adaptive", display: "summarized" }
- output_config: { effort: "xhigh" } ← premium tier
- context_management: { edits: [...] }
These extras request features that bill against Anthropic "extra usage" even
on Claude Max subscriptions. Anthropic gates the request:
400 "You're out of extra usage. Add more at claude.ai/settings/usage"
The existing strip lives in BaseExecutor's Claude OAuth cloak block, but the
CliproxyapiExecutor extends BaseExecutor without inheriting that cloak (the
block is gated on this.provider === "claude", but in CPA mode chatCore swaps
the executor to "cliproxyapi" entirely). So Capy's extras reach CPA verbatim
and CPA forwards them.
Fix: in CliproxyapiExecutor.transformRequest(), when the body is in
Anthropic shape (i.e. about to be POSTed to CPA's /v1/messages), strip the
three extras. CPA still applies its own Claude Code wire-image cloak (CCH
signing, billing header, system sentinel, uTLS) downstream. OpenAI-shape
bodies are untouched.
Mirrors the runtime "Patch I2/I4" effect previously applied via patch.mjs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When chatCore detects target=claude (source format = claude), it skips the
openai translation and passes the Anthropic-shape body straight to the
cliproxyapi executor. The executor's buildUrl() was hardcoded to
/v1/chat/completions, so CPA received an Anthropic body on the OpenAI
endpoint. CPA responded with an OpenAI-style SSE stream (choices[].message)
which Anthropic SDK clients (Capy, claude-cli, etc.) cannot parse —
server-side 200 with "request ended without sending any chunks" client-side.
Fix: detect Anthropic body shape (top-level `system` as array OR
messages[0].content as array) in execute() and route to /v1/messages.
CPA natively supports both endpoints; routing to /v1/messages preserves
the wire shape end-to-end. No translator round-trip required.
OpenAI Chat passthrough cases (Capy in OpenAI API mode, Codex CLI, etc.)
hit the false branch and keep the previous /v1/chat/completions route.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OpenAI Responses API supports background: true for async/deferred runs
(returns 202 with response_id + GET /responses/<id> polling). Implementing
the full background contract requires queue/poll infrastructure that
OmniRoute does not have as a forward proxy.
Previously the translator rejected such requests with HTTP 400
"Unsupported Responses API feature: background mode is not supported by
omniroute". This breaks clients that opportunistically set background=true
for long-running tasks (Capy Captain Pro, Codex agents) even when the
underlying execution would complete in a normal HTTP timeout window.
Degrade silently: strip the background flag and run synchronously. The
client receives the full response in one round-trip with status=completed.
Clients that strictly require the async contract still observe a completed
response on the first poll and can adapt.
The existing test that asserted background=true throws is split into two:
- the unsupported-tool-type branch keeps the throw assertion
- the background branch now asserts the flag is stripped and translation
completes with the expected messages
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The codex OAuth backend (chatgpt.com/backend-api/codex/responses) caps
the gpt-5.5 context window at 400 000 tokens, not the 1 050 000 advertised
by the public OpenAI API. Clients reading /v1/models for prompt-budget
math (e.g. Capy computing compression thresholds) would otherwise allow
prompts beyond the OAuth backend's actual capacity, triggering
silent truncation or upstream 400s.
Per-entry override of GPT_5_5_CODEX_CAPABILITIES (which spreads
contextLength=1050000 from the shared constant) — the constant itself
is unchanged because it's also consumed by the github provider's
gpt-5.5 entry where DB sync provides the canonical 400K/128K values.
max_output_tokens=128000 is informational only — the codex OAuth
backend strips max_output_tokens server-side (LiteLLM and Codex CLI
both confirm this). Advertising a realistic value still helps clients
that read it for token-budget heuristics.
References :
- openai/codex#19208 "1M context window gone after GPT-5.5 release"
- openai/codex#19319 — 258 400 effective context window observation
- openai/codex#19464 — Support 1M for GPT-5.5 in Codex (feature ask)
- opencode#24171 — GPT-5.5 Codex 400K vs API 1M
- BerriAI/litellm#21193 — codex backend rejects unsupported params
- openai/codex#4138 — model_max_output_tokens not wired up
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The /v1/models catalog endpoint reads `model.contextLength` directly from
the REGISTRY entries (src/app/api/v1/models/catalog.ts:823) and falls back
to REGISTRY[provider].defaultContextLength → getTokenLimit chain otherwise.
`max_output_tokens` flows through enrichCatalogModelEntry which calls
getResolvedModelCapabilities (src/lib/modelCapabilities.ts:261-265) reading
the same per-model registry fields.
Currently most non-default-spec models lack explicit contextLength /
maxOutputTokens, so /v1/models advertises stale defaults
(DEFAULT_LIMITS.default = 128000, MODEL_SPECS.__default__.maxOutputTokens
= 8192). Clients that read these values to compute compression thresholds
or prompt budgets (e.g. Capy reading context_length for its compress
trigger) over-aggressively trim long conversations.
Provider IDs in the registry (`claude`, `kimi-coding`, `xiaomi-mimo`) do
not match the canonical IDs synced from models.dev (`anthropic`, `cc`,
`xiaomi`), so the DB lookup misses for these entries. The cleanest fix
is to populate the registry directly with values consensus'd across 6+
upstream sync sources (anthropic, cc, openrouter, kilocode, vercel,
xiaomi-token-plan-*, llmgateway, kc, kilo-gateway).
Values:
- claude (id="claude", alias="cc"):
opus-4-7, opus-4-6 → 1M ctx / 128K max_out
opus-4-5-20251101, sonnet-4-6, sonnet-4-5-20250929, haiku-4-5-20251001
→ 200K ctx / 64K max_out
- kiro (alias="kr"): same opus/sonnet/haiku tiering
- github (alias="gh"): same claude subset tiering
- KIMI_CODING_SHARED:
kimi-k2.6, kimi-k2.6-thinking → 262K ctx / 262K max_out
- xiaomi-mimo:
mimo-v2.5-pro, mimo-v2.5 → 1M ctx / 131K max_out
mimo-v2-omni → 262K ctx / 131K max_out
mimo-v2-flash → 262K ctx / 65K max_out
The 1M context tier for claude-opus-4-6/4-7 requires the
`context-1m-2025-08-07` beta header which selectBetaFlags() already
attaches automatically for full-agent traffic (hasTools && hasSystem,
the Capy pattern) — see open-sse/executors/claudeIdentity.ts:304. No
wire-level change needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Kimi Code OAuth product (https://api.kimi.com/coding/v1/messages) runs
on the Kimi K2.6 backbone, which has a 262144 (256K) native context window
per Moonshot's published platform docs. The KIMI_CODING_SHARED registry
block had no defaultContextLength set, and the OAuth Coding plan is not
covered by the models.dev sync (no rows in model_capabilities), so
contextManager.ts:getTokenLimit fell through to DEFAULT_LIMITS.default
= 128000 — half the actual model capacity.
This propagates to /v1/models advertised context_length=128000 for
kimi-coding/kimi-k2.6 and kmc/kimi-k2.6, which under-reports the prompt
budget to downstream clients. Capy and similar clients that compute
prompt_cap = context_length - request.max_tokens then end up with an
artificially low cap (128K - 64K = 64K for Captain agent), causing
premature plateau in long conversations.
Reference: cross-provider model_capabilities rows in the DB confirm
the 262144 value (openrouter/moonshotai/kimi-k2.6, moonshot/kimi-k2.6,
ali/kimi-k2.6, deepinfra/moonshotai/Kimi-K2.6, hf/moonshotai/Kimi-K2.6,
and ~30 others). 128000 was a per-provider-default underbid only because
the OAuth Coding plan never gets a sync row.
The model_capabilities DB rows for synced providers override this
default, so any future syncing or per-model overrides still work as
intended.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The claude→openai translator emits reasoning_effort=xhigh when the client
sends output_config.effort=max on a Claude-shape request. Combined with
runtime alias remapping (e.g. claude-opus-4-6 → mimo/mimo-v2.5-pro), this
routes xhigh to OpenAI-shape providers that only accept low|medium|high:
xiaomi-mimo 400 reasoning_effort: Input should be 'low','medium' or 'high'
mistral/devstral 400 unrecognized field reasoning_effort
github/claude-* 400 unrecognized field reasoning_effort
Each rejection burns a combo fallback attempt before reaching a working
provider, adding latency per turn and polluting logs.
Add provider-aware sanitation in BaseExecutor.execute after transformRequest:
- xiaomi-mimo and other non-xhigh providers: downgrade xhigh → high
(gated by the supportsXHighEffort helper from providerModels.ts, so
models that genuinely expose xhigh pass through unchanged)
- mistral/devstral, github/claude|haiku|oswe: strip reasoning_effort entirely
The hook runs after transformRequest so per-provider transforms that
reintroduce reasoning_effort are also caught before fetch.
Includes unit tests covering: xhigh downgrade (top-level and nested),
strip-entirely for rejecting providers, no-op when effort is absent,
no-op for non-object bodies, and pass-through for unknown providers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
prepareClaudeRequest already injects a synthetic thinking content block
when body.thinking is enabled, an assistant turn contains a tool_use,
and no precursor thinking block exists in content[]. The injection was
gated by `provider === "claude" || provider?.startsWith?.("anthropic-
compatible-")`, which excluded other Anthropic-shape upstreams:
- kimi-coding (api.kimi.com/coding/v1/messages)
- glmt (z.ai coding plan Anthropic endpoint)
- zai, agentrouter, and any future provider registered with format: "claude"
These providers enforce the same body-shape contract for thinking mode
and reject multi-turn requests with errors like:
kimi-coding: "thinking is enabled but reasoning_content is missing in
assistant tool call message at index N"
claude (cross-provider replay): "Invalid signature in thinking block"
prepareClaudeRequest is only invoked when targetFormat === FORMATS.CLAUDE
(translator/index.ts:165-168), so the outer provider gate is redundant:
any body reaching this function is destined for a Claude-shape upstream.
Drop the gate; the body of the block executes uniformly for all Claude-
shape targets.
Net effect:
- Single-turn requests unchanged (no tool_use in history yet)
- Multi-turn requests with assistant tool_use turns get a placeholder
thinking block ("." with DEFAULT_THINKING_CLAUDE_SIGNATURE) prepended
- Existing thinking blocks get their signature replaced with the default
(already the case for claude/anthropic-compatible-*; now applied to
kimi-coding and similar — same defensive cross-provider posture)
The `supportsPromptCaching` gate at line 152 (for cache_control insertion)
remains unchanged — that one still wants to be conservative about which
providers support prompt caching.
Includes 5 unit test cases covering: claude native injection (regression),
kimi-coding injection (new), existing-thinking signature replacement,
thinking-off pass-through, and single-turn no-inject.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mandate direct PR fixes over cherry-picking in all cases where the maintainer has write access to the contributor's branch. Explicitly forbid using cherry-pick just to bypass conflict resolution.