Skip to content

Commit 8c3b4ec

Browse files
haasonsaasclaude
andcommitted
Fix LLM routing bug and container permission errors
The default model `anthropic/claude-sonnet-4.6` was being routed to OpenRouter (due to the `/` in the name) instead of the Anthropic API, causing all review requests to fail with "Failed to send request to OpenAI". Fix by: 1. Changing default model to `claude-sonnet-4-6` (no vendor prefix) 2. Adding `anthropic/` vendor prefix routing to AnthropicAdapter (other vendor prefixes still route to OpenRouter) Also fix container permission denied errors by adding a non-root user (UID 1000) to the Dockerfile with pre-created data directories, matching the k8s securityContext that enforces runAsUser: 1000. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1d13325 commit 8c3b4ec

6 files changed

Lines changed: 42 additions & 11 deletions

File tree

Dockerfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,17 @@ FROM alpine:3.19
3333

3434
RUN apk add --no-cache ca-certificates git
3535

36+
RUN addgroup -g 1000 diffscope && \
37+
adduser -u 1000 -G diffscope -h /home/diffscope -s /bin/sh -D diffscope && \
38+
mkdir -p /home/diffscope/.local/share/diffscope \
39+
/home/diffscope/.diffscope && \
40+
chown -R diffscope:diffscope /home/diffscope
41+
3642
COPY --from=builder /app/target/release/diffscope /usr/local/bin/diffscope
3743

44+
USER diffscope
45+
WORKDIR /home/diffscope
46+
3847
EXPOSE 3000
3948

4049
ENTRYPOINT ["diffscope"]

charts/diffscope/values.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ serviceAccount:
1818
diffscope:
1919
host: "0.0.0.0"
2020
port: 3000
21-
model: "anthropic/claude-sonnet-4.6"
21+
model: "claude-sonnet-4-6"
2222
# Adapter override: openai, anthropic, ollama, openrouter (auto-detected if empty)
2323
adapter: ""
2424
# Base URL for LLM API (auto-set to Ollama service URL when ollama.enabled)

src/adapters/llm.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub struct ModelConfig {
2626
impl Default for ModelConfig {
2727
fn default() -> Self {
2828
Self {
29-
model_name: "anthropic/claude-sonnet-4.6".to_string(),
29+
model_name: "claude-sonnet-4-6".to_string(),
3030
api_key: None,
3131
base_url: None,
3232
temperature: 0.2,
@@ -98,13 +98,26 @@ pub fn create_adapter(config: &ModelConfig) -> Result<Box<dyn LLMAdapter>> {
9898
};
9999
}
100100

101-
// OpenRouter-style model IDs (vendor/model)
101+
// Vendor-prefixed model IDs (vendor/model)
102102
if config.model_name.contains('/') {
103-
let mut or_config = config;
104-
if or_config.base_url.is_none() {
105-
or_config.base_url = Some("https://openrouter.ai/api/v1".to_string());
103+
let (vendor, model) = config.model_name.split_once('/').unwrap();
104+
let model = model.to_string();
105+
match vendor {
106+
"anthropic" => {
107+
// Route anthropic/ prefix directly to the Anthropic adapter
108+
let mut anth_config = config;
109+
anth_config.model_name = model;
110+
return Ok(Box::new(crate::adapters::AnthropicAdapter::new(anth_config)?));
111+
}
112+
_ => {
113+
// Other vendor prefixes (e.g. openai/, meta-llama/) → OpenRouter
114+
let mut or_config = config;
115+
if or_config.base_url.is_none() {
116+
or_config.base_url = Some("https://openrouter.ai/api/v1".to_string());
117+
}
118+
return Ok(Box::new(crate::adapters::OpenAIAdapter::new(or_config)?));
119+
}
106120
}
107-
return Ok(Box::new(crate::adapters::OpenAIAdapter::new(or_config)?));
108121
}
109122

110123
// Model-name heuristic
@@ -306,10 +319,19 @@ mod tests {
306319
let _adapter = create_adapter(&config).unwrap();
307320
}
308321

322+
#[test]
323+
fn test_create_adapter_anthropic_vendor_prefix() {
324+
// anthropic/ prefix should route to AnthropicAdapter, not OpenRouter
325+
let config = local_config("anthropic/claude-sonnet-4-6");
326+
let adapter = create_adapter(&config).unwrap();
327+
// The adapter strips the vendor prefix
328+
assert_eq!(adapter.model_name(), "claude-sonnet-4-6");
329+
}
330+
309331
#[test]
310332
fn test_model_config_default() {
311333
let config = ModelConfig::default();
312-
assert_eq!(config.model_name, "anthropic/claude-sonnet-4.6");
334+
assert_eq!(config.model_name, "claude-sonnet-4-6");
313335
assert!(config.api_key.is_none());
314336
assert!(config.base_url.is_none());
315337
assert!((config.temperature - 0.2).abs() < f32::EPSILON);

src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -968,7 +968,7 @@ impl Config {
968968
}
969969

970970
fn default_model() -> String {
971-
"anthropic/claude-sonnet-4.6".to_string()
971+
"claude-sonnet-4-6".to_string()
972972
}
973973

974974
fn default_temperature() -> f32 {

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ struct Cli {
2626
#[command(subcommand)]
2727
command: Commands,
2828

29-
#[arg(long, global = true, default_value = "anthropic/claude-sonnet-4.6")]
29+
#[arg(long, global = true, default_value = "claude-sonnet-4-6")]
3030
model: String,
3131

3232
#[arg(

web/src/pages/Settings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ export function Settings() {
473473
</div>
474474

475475
<div className="space-y-3 border-t border-border-subtle pt-3">
476-
{field('Model name', 'model', 'text', 'anthropic/claude-sonnet-4.6', 'OpenRouter: vendor/model-name')}
476+
{field('Model name', 'model', 'text', 'claude-sonnet-4-6', 'Direct: claude-*, gpt-* | OpenRouter: vendor/model')}
477477
{selectField('Adapter', 'adapter', [
478478
{ value: '', label: 'Auto-detect' },
479479
{ value: 'openai', label: 'OpenAI (direct)' },

0 commit comments

Comments
 (0)