Skip to content

fix(server): use a custom model only when the Claude product model is unavailable - #85

Closed
macodev00 wants to merge 1 commit into
mainfrom
cursor/text-gen-fallback-model-redo3-2a3a
Closed

macodev00 wants to merge 1 commit into
mainfrom
cursor/text-gen-fallback-model-redo3-2a3a

Conversation

@macodev00

Copy link
Copy Markdown
Owner

Problem

One-shot text generation (commit, PR, branch, and title) uses the product slug when textGenerationModelSelection is unset and the stored instance is disabled. For Claude that slug is claude-haiku-4-5. A backend that refuses that slug, such as an OpenRouter workspace whose guardrails block Anthropic models, fails the spawn even when the Claude instance has a working custom model.

The product slug is still the right default when that model works. Selecting a configured custom model in its place would change commit, PR, branch, and title generation for healthy setups.

A content or prompt guardrail failure is a different failure. Retrying it with a custom model would hide the original result.

Change

fallbackTextGenerationProvider still writes the product text-generation slug. Settings, and the first one-shot attempt, stay on that slug when a custom model is configured.

Claude one-shot generation retries once with the instance's first configured custom model only after the product-slug attempt exits non-zero and the CLI output says the model itself is unavailable (unknown model, model not found, model blocked by guardrail, the OpenRouter "N endpoints out of M" plus "endpoints excluded" pair, and the same class of failure). A bare guardrail match does not qualify, so a content or prompt guardrail stays on the original attempt.

A successful product-slug attempt is unchanged. An explicit non-product model, an instance with no other custom model, and a non-model failure do not retry.

When the CLI exits non-zero, TextGenerationError.detail is a bounded category (model_unavailable or cli_failed), the exit code, and api_status only when stdout JSON carries an integer HTTP status from 100 through 599. Stdout and stderr are not copied into detail. The process exit is retained as cause (Claude CLI process exited with code N) and that message does not include process output. The retry log records operation and the product model. It does not record customModel.

Commit, PR, branch, and title generation share runClaudeJson, so the same rule covers all four.

Addresses pingdotgg#12651

Scope and approval

pingdotgg#12651 is an accepted server bug. Julius confirmed the fallback sends claude-haiku-4-5 and does not consult the instance's configured custom model: pingdotgg#12651 (comment)

The accepted direction is to keep the cheap product slug when it is available, and not to send it when that slug is what the backend refuses. This PR does the second half only after the product attempt reports the model itself is unavailable, so healthy defaults stay on the product slug.

This replaces the closed approach in pingdotgg#14747. That review required a bounded failure category (with the process exit kept as cause), no customModel log annotation, and a fallback predicate that does not treat every guardrail string as a missing model.

Verification

From /workspace at 17fdefbfd, Node v24.13.1, workspace vp 1.0.0 (/workspace/node_modules/.bin ahead of PATH):

vp test run apps/server/src/textGeneration/TextGenerationModelFallback.test.ts apps/server/src/textGeneration/ClaudeTextGeneration.test.ts apps/server/src/serverSettings.test.ts

Result: 3 files passed, 94 tests passed (3.89s). Coverage includes a healthy product-slug success while a custom model is configured (one spawn, product slug only), a model-blocked claude-haiku-4-5 followed by one retry on z-ai/glm-5.3-flash, a content-guardrail failure that does not retry and whose detail and cause omit the CLI payload, a non-model product-slug failure that does not retry, an unavailable product slug with no custom model that reports model_unavailable without the CLI text, an explicit non-product model that does not retry, and settings resolution that still returns the Claude product slug when a custom model and a custom thread default are configured.

vp run --filter t3 typecheck

Result: exit 0. The command printed pre-existing effect suggestions in CodexInstallation.ts, CodexAdapter.test.ts, CodexProvider.ts, GitHubCli.test.ts, and ws.ts. None of those files are in this diff.

vp lint apps/server/src/textGeneration/TextGenerationModelFallback.ts apps/server/src/textGeneration/TextGenerationModelFallback.test.ts apps/server/src/textGeneration/ClaudeTextGeneration.ts apps/server/src/textGeneration/ClaudeTextGeneration.test.ts apps/server/src/serverSettings.ts apps/server/src/serverSettings.test.ts

Result: exit 0. The only output was two existing no-inline-schema-compile warnings in serverSettings.test.ts around the pre-existing inline schema helpers, not in this diff.

vp fmt apps/server/src/textGeneration/TextGenerationModelFallback.ts apps/server/src/textGeneration/TextGenerationModelFallback.test.ts apps/server/src/textGeneration/ClaudeTextGeneration.ts apps/server/src/textGeneration/ClaudeTextGeneration.test.ts apps/server/src/serverSettings.ts apps/server/src/serverSettings.test.ts

Result: finished on the touched files with no further edits.

Limitations

Linux only. The unavailable product slug is attempted once before the custom model, so a broken fallback pays for one failed CLI spawn. The substitute is the first configured custom model on that Claude instance other than the product slug; a built-in thread default is not used. Codex, Cursor, Grok, OpenCode, and Antigravity still send their product slug and do not retry. The surfaced error is the bounded category, exit code, and optional HTTP status. The raw stdout API sentence is not included, because that text can carry credentials or arbitrary payloads. A live OpenRouter spawn was not run.

UI Changes

None. This only changes server-side model selection and CLI failure reporting for one-shot commit, PR, branch, and title generation.

Grok 4.7, Cursor cloud agent.

Open in Web Open in Cursor 

… is unavailable

One-shot commit, PR, branch, and title generation still send the product
slug first. A configured custom model is used only when that attempt
reports the model itself is unavailable. Content and prompt guardrail
failures stay failed. CLI errors expose a bounded category, and the
process exit is kept as cause.

Refs pingdotgg#12651

Co-authored-by: maco <macodev00@users.noreply.github.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L labels Oct 2, 2026
@macodev00

Copy link
Copy Markdown
Owner Author

Not opening upstream: redo cap exceeded for pingdotgg#12651.

@macodev00 macodev00 closed this Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants