Repository navigation
Conversation
[Medium impact] The changes affect outbound requests in the OpenAI-compatible and Anthropic provider workflows. The PR should not merge while a built-in catalog refresh can send a named profile’s private headers to another service. Findings
Fix with agent prompt### Issue 1
crates/jcode-provider-openrouter-runtime/src/lib.rs:704-708
The background catalog refresh uses a built-in provider’s endpoint but borrows headers from any named profile with the same ID. If `GROQ_API_KEY` is configured and `[providers.groq]` points to a corporate gateway, a missing or stale built-in catalog causes the gateway’s private headers to be sent to `https://api.groq.com/openai/v1/models`. This exposes gateway credentials or tenant information to a different service, even though the named profile specifies another endpoint.
Keep the endpoint and headers tied to the same profile. Built-in refreshes should not borrow named-profile headers. Fix this before merging.
**How this was verified:** A locally intercepted request preserved the Groq destination and carried both dummy corporate headers at the PR head, while the same request at the base carried neither.
### Issue 2
crates/jcode-provider-anthropic-runtime/src/catalog.rs:undefined-116
`refresh_model_catalog_for_route` reloads current settings instead of using the provider’s saved `direct_transport.headers`. Named Anthropic profiles clear `JCODE_ANTHROPIC_HEADERS` immediately after constructing the provider, so discovery falls back to global headers while chat retains the profile overrides. The same mismatch exists in `fetch_live_model_ids_for_doctor`.
For a named profile using the official Anthropic endpoint, captured requests confirmed that chat sends the profile’s overridden `x-api-key` and succeeds, while both catalog paths send the underlying key and receive 401 responses. A valid underlying key for another account could instead produce an account-inconsistent catalog. This is a non-blocking discovery inconsistency; use the provider’s saved headers in both direct catalog paths.
### Issue 3
crates/jcode-provider-openrouter-runtime/src/openrouter_tests.rs:undefined-3546
- **Bug**
- Removing the catalog_auth module declaration disconnects catalog_auth_tests.rs from the compiled test module graph. The base lists and passes catalog_request_headers_follow_provider_auth_rules; the rebuilt head lists and runs zero matching tests, and no replacement inclusion was found. This is a non-blocking loss of regression coverage for catalog request authentication rules, not evidence of an actual product authentication failure.
- **Cause**
- The full PR diff removes #[path = "catalog_auth_tests.rs"] mod catalog_auth; from the end of openrouter_tests.rs. The test definition remains in catalog_auth_tests.rs:4 without a replacement module inclusion.
- **Fix**
- Restore the catalog_auth module declaration under the existing test module, or include the test file elsewhere with the appropriate scope, then verify that enumeration lists the test and filtered execution runs it.
### Issue 4
crates/jcode-provider-anthropic-runtime/src/catalog.rs:116-119
Changing `[provider.headers].x-api-key` changes the credential used for direct Anthropic requests, but the catalog cache scope still hashes the underlying key. After changing the override and opening a new provider session, a fresh catalog from the previous account suppresses discovery. If the accounts expose different models, the picker therefore keeps showing the previous account’s models while chat authenticates as the new account. A forced refresh corrects the list, but still writes it under the same scope. This is a non-blocking discovery inconsistency; include the effective credential in the cache identity used by both catalog refresh and readers.
### Issue 5
crates/jcode-provider-openrouter-runtime/src/catalog_fetch.rs:undefined-54
Changing a gateway’s routing or account header leaves its old model list valid. These overrides reach discovery requests, but the saved catalog distinguishes only the endpoint and profile. If a user changes `x-route` or `Authorization` and opens a new session at the same endpoint, `fetch_models` returns the previous route’s cached models. A cache younger than 15 minutes also skips background discovery.
This is a non-blocking discovery inconsistency: the picker can offer models the new route cannot use. Include a fingerprint of the effective headers in the saved catalog identity and check it before reuse.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.SummaryAdds configurable
|
0869b46 to
b5f16be
Compare
b5f16be to
43e1b98
Compare
ec6e115 to
474303e
Compare
7898d74 to
2bc80a2
Compare
|
Rebased. The branch now sits on #1906's head (0ba439f, master + the red-master repairs), since plain master fails the same gates this PR needs green: its own latest push run 41177474980 has Quality Guardrails and all three Build & Test jobs red, and the branch bases off that. Diff vs master is unchanged in substance: config-driven user_agent + headers for the default provider and named profiles, plus the tests. Conflict with master's new browser_handoff config test resolved by keeping both. Also folded in: two field_reassign_with_default clippy fixes in the branch's own tests (provider_catalog_tests, openrouter_tests) that the current clippy flags, and the ratchet baselines refreshed for the branch's own growth only (panic 175->182 via ollama_context, swallowed-error 3963->3966, code/test-size for openrouter lib+tests). Local: clippy -D warnings clean on all touched crates, fmt clean, budgets green, openrouter + provider_catalog suites pass. One provider_catalog test fails only under ambient JCODE_PROVIDER_PROFILE_ACTIVE/JCODE_NAMED_PROVIDER_PROFILE dev env, passes with them unset; that isolation is #1882's scope, not this change. |
| jcode_base::provider::fetch_anthropic_model_catalog(&self.direct_api_key()?).await? | ||
| // Same header-override parity as the doctor path: chat honors | ||
| // `[provider.headers]`, so the direct catalog refresh must too. | ||
| let headers = super::configured_direct_headers(); |
There was a problem hiding this comment.
Discovery loses profile headers
refresh_model_catalog_for_route reloads current settings instead of using the provider’s saved direct_transport.headers. Named Anthropic profiles clear JCODE_ANTHROPIC_HEADERS immediately after constructing the provider, so discovery falls back to global headers while chat retains the profile overrides. The same mismatch exists in fetch_live_model_ids_for_doctor.
For a named profile using the official Anthropic endpoint, captured requests confirmed that chat sends the profile’s overridden x-api-key and succeeds, while both catalog paths send the underlying key and receive 401 responses. A valid underlying key for another account could instead produce an account-inconsistent catalog. This is a non-blocking discovery inconsistency; use the provider’s saved headers in both direct catalog paths.
Knowledge Base Used: Provider selection and runtime adapters
Artifacts
Executed Rust test for named-profile header retention
- The authored test constructs an official-endpoint named profile, clears its environment bridge, and executes chat and both catalog paths, asserting the observed mismatch and saved-header control.
Executed local request-capture runner
- The authored runner provides a local TLS fixture, runs the same test before and after a temporary saved-header change, captures responses, and restores tracked source files.
Request captures with the supplied HEAD implementation
- The HEAD run captures successful chat and unauthorized refresh and doctor-facing catalog requests, proving that only chat retains the named-profile key override.
Request captures with the temporary saved-header control
- The control run captures HTTP/1.1 200 OK for all three requests and records the temporary source diff, showing that using saved headers removes the mismatch.
Ran code and verified through TREX
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-anthropic-runtime/src/catalog.rs
Line: 116
Comment:
**Discovery loses profile headers**
`refresh_model_catalog_for_route` reloads current settings instead of using the provider’s saved `direct_transport.headers`. Named Anthropic profiles clear `JCODE_ANTHROPIC_HEADERS` immediately after constructing the provider, so discovery falls back to global headers while chat retains the profile overrides. The same mismatch exists in `fetch_live_model_ids_for_doctor`.
For a named profile using the official Anthropic endpoint, captured requests confirmed that chat sends the profile’s overridden `x-api-key` and succeeds, while both catalog paths send the underlying key and receive 401 responses. A valid underlying key for another account could instead produce an account-inconsistent catalog. This is a non-blocking discovery inconsistency; use the provider’s saved headers in both direct catalog paths.
**Knowledge Base Used:** [Provider selection and runtime adapters](https://app.greptile.com/-/switch-tenant?tenant=solo-systems&next=%2Fcustom-context%2Fknowledge-base%2F1jehuang%2Fjcode%2F-%2Fdocs%2Fprovider-selection-and-runtime-adapters.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.2bc80a2 to
a601f1d
Compare
| @@ -3539,6 +3544,3 @@ fn venice_disables_venice_system_prompt_by_default_but_user_can_override() { | |||
| ); | |||
| assert!(OpenRouterProvider::with_profile_default_extra_body(Some("cerebras"), None).is_none()); | |||
| } | |||
There was a problem hiding this comment.
Catalog auth test stops running
- Bug
- Removing the catalog_auth module declaration disconnects catalog_auth_tests.rs from the compiled test module graph. The base lists and passes catalog_request_headers_follow_provider_auth_rules; the rebuilt head lists and runs zero matching tests, and no replacement inclusion was found. This is a non-blocking loss of regression coverage for catalog request authentication rules, not evidence of an actual product authentication failure.
- Cause
- The full PR diff removes #[path = "catalog_auth_tests.rs"] mod catalog_auth; from the end of openrouter_tests.rs. The test definition remains in catalog_auth_tests.rs:4 without a replacement module inclusion.
- Fix
- Restore the catalog_auth module declaration under the existing test module, or include the test file elsewhere with the appropriate scope, then verify that enumeration lists the test and filtered execution runs it.
a601f1d to
b4f0c4f
Compare
| let headers = super::configured_direct_headers(); | ||
| jcode_base::provider::fetch_anthropic_model_catalog_with_headers( | ||
| &self.direct_api_key()?, | ||
| headers.ok().as_ref(), |
There was a problem hiding this comment.
Models retain previous account
Changing [provider.headers].x-api-key changes the credential used for direct Anthropic requests, but the catalog cache scope still hashes the underlying key. After changing the override and opening a new provider session, a fresh catalog from the previous account suppresses discovery. If the accounts expose different models, the picker therefore keeps showing the previous account’s models while chat authenticates as the new account. A forced refresh corrects the list, but still writes it under the same scope. This is a non-blocking discovery inconsistency; include the effective credential in the cache identity used by both catalog refresh and readers.
Knowledge Base Used:
Artifacts
- The executed test exercises real provider scope, cache admission, picker results, and HTTP requests against isolated account-specific responses, demonstrating the credential-to-cache mismatch.
- The executed runner reconstructs the PR parent and HEAD, injects the identical test and local endpoint seam, captures both command outputs, and restores tracked source bytes.
- The pre-change test captured catalog and chat requests receiving HTTP/1.1 200 OK while all retained account A authentication, establishing the baseline.
- The HEAD test captured successful account B chat, suppressed normal catalog refresh, and successful forced account B discovery, confirming that the unchanged cache identity causes stale account A models.
Ran code and verified through TREX
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-anthropic-runtime/src/catalog.rs
Line: 116-119
Comment:
**Models retain previous account**
Changing `[provider.headers].x-api-key` changes the credential used for direct Anthropic requests, but the catalog cache scope still hashes the underlying key. After changing the override and opening a new provider session, a fresh catalog from the previous account suppresses discovery. If the accounts expose different models, the picker therefore keeps showing the previous account’s models while chat authenticates as the new account. A forced refresh corrects the list, but still writes it under the same scope. This is a non-blocking discovery inconsistency; include the effective credential in the cache identity used by both catalog refresh and readers.
**Knowledge Base Used:**
- [Model provider integration](https://app.greptile.com/-/switch-tenant?tenant=solo-systems&next=%2Fcustom-context%2Fknowledge-base%2F1jehuang%2Fjcode%2F-%2Fdocs%2Fprovider-integration.md)
- [Provider selection and runtime adapters](https://app.greptile.com/-/switch-tenant?tenant=solo-systems&next=%2Fcustom-context%2Fknowledge-base%2F1jehuang%2Fjcode%2F-%2Fdocs%2Fprovider-selection-and-runtime-adapters.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.c48b937 to
00748ff
Compare
| resolved_profile_http_header_overrides( | ||
| &jcode_base::config::config().provider, | ||
| jcode_base::config::config().providers.get(&profile_id), | ||
| &profile_id, | ||
| ) |
There was a problem hiding this comment.
Private headers reach another service
The background catalog refresh uses a built-in provider’s endpoint but borrows headers from any named profile with the same ID. If GROQ_API_KEY is configured and [providers.groq] points to a corporate gateway, a missing or stale built-in catalog causes the gateway’s private headers to be sent to https://api.groq.com/openai/v1/models. This exposes gateway credentials or tenant information to a different service, even though the named profile specifies another endpoint.
Keep the endpoint and headers tied to the same profile. Built-in refreshes should not borrow named-profile headers. Fix this before merging.
How this was verified: A locally intercepted request preserved the Groq destination and carried both dummy corporate headers at the PR head, while the same request at the base carried neither.
Knowledge Base Used: Provider selection and runtime adapters
Artifacts
Executed Rust test for the mismatched profile and endpoint
- The executed test verifies built-in eligibility and the differing gateway URL, calls the real refresh function, and confirms the intercepted response was consumed.
Executed before and after runner with safe local TLS interception
- The runner executes both full-PR revisions with dummy credentials and a non-forwarding interceptor, demonstrating the header change without contacting public providers.
Before-change request without corporate headers
- The passing base-revision run captures the canonical Groq models request and full local HTTP 200 OK response, showing that corporate headers were absent.
After-change request carrying corporate headers to Groq
- The passing head-revision run captures both dummy corporate headers on the canonical Groq models request and its full local HTTP 200 OK response, confirming the destination mismatch.
Production caller trace and tracked-file restoration check
- Captured commands show startup hook registration, scheduler eligibility checks, the changed header lookup, and a clean tracked diff, establishing realistic reachability separately from the direct runtime test.
Executed artifact upload script
- The script uploads the authored sources and observed evidence through assigned pre-signed slots and records their exact authenticated artifact references.
Successful upload receipts for sources and evidence
- The captured upload command exited 0 and recorded HTTP 200 OK receipts with artifact IDs and URLs, confirming that the evidence was uploaded rather than merely saved locally.
- The executed test verifies built-in eligibility and the differing gateway URL, calls the real refresh function, and confirms the intercepted response was consumed.
- The runner executes both full-PR revisions with dummy credentials and a non-forwarding interceptor, demonstrating the header change without contacting public providers.
- The passing base-revision run captures the canonical Groq models request and full local HTTP 200 OK response, showing that corporate headers were absent.
- The passing head-revision run captures both dummy corporate headers on the canonical Groq models request and its full local HTTP 200 OK response, confirming the destination mismatch.
- Captured commands show startup hook registration, scheduler eligibility checks, the changed header lookup, and a clean tracked diff, establishing realistic reachability separately from the direct runtime test.
Ran code and verified through TREX
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-openrouter-runtime/src/lib.rs
Line: 704-708
Comment:
**Private headers reach another service**
The background catalog refresh uses a built-in provider’s endpoint but borrows headers from any named profile with the same ID. If `GROQ_API_KEY` is configured and `[providers.groq]` points to a corporate gateway, a missing or stale built-in catalog causes the gateway’s private headers to be sent to `https://api.groq.com/openai/v1/models`. This exposes gateway credentials or tenant information to a different service, even though the named profile specifies another endpoint.
Keep the endpoint and headers tied to the same profile. Built-in refreshes should not borrow named-profile headers. Fix this before merging.
**How this was verified:** A locally intercepted request preserved the Groq destination and carried both dummy corporate headers at the PR head, while the same request at the base carried neither.
**Knowledge Base Used:** [Provider selection and runtime adapters](https://app.greptile.com/-/switch-tenant?tenant=solo-systems&next=%2Fcustom-context%2Fknowledge-base%2F1jehuang%2Fjcode%2F-%2Fdocs%2Fprovider-selection-and-runtime-adapters.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.00748ff to
585e981
Compare
85ae421 to
a7c1db3
Compare
a7c1db3 to
5d0bb94
Compare
| .await | ||
| // Config-driven overrides apply last so they win over any same-named | ||
| // header jcode set earlier on the request. | ||
| .headers(http_header_overrides.clone()) |
There was a problem hiding this comment.
Old route models remain visible
Changing a gateway’s routing or account header leaves its old model list valid. These overrides reach discovery requests, but the saved catalog distinguishes only the endpoint and profile. If a user changes x-route or Authorization and opens a new session at the same endpoint, fetch_models returns the previous route’s cached models. A cache younger than 15 minutes also skips background discovery.
This is a non-blocking discovery inconsistency: the picker can offer models the new route cannot use. Include a fingerprint of the effective headers in the saved catalog identity and check it before reuse.
Knowledge Base Used: Model provider integration
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-openrouter-runtime/src/catalog_fetch.rs
Line: 54
Comment:
**Old route models remain visible**
Changing a gateway’s routing or account header leaves its old model list valid. These overrides reach discovery requests, but the saved catalog distinguishes only the endpoint and profile. If a user changes `x-route` or `Authorization` and opens a new session at the same endpoint, `fetch_models` returns the previous route’s cached models. A cache younger than 15 minutes also skips background discovery.
This is a non-blocking discovery inconsistency: the picker can offer models the new route cannot use. Include a fingerprint of the effective headers in the saved catalog identity and check it before reuse.
**Knowledge Base Used:** [Model provider integration](https://app.greptile.com/-/switch-tenant?tenant=solo-systems&next=%2Fcustom-context%2Fknowledge-base%2F1jehuang%2Fjcode%2F-%2Fdocs%2Fprovider-integration.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.5d0bb94 to
6ed61d7
Compare
Lets providers send a custom User-Agent and extra headers from config. Useful for gateways that route or auth on header values instead of query params. Applies to the openrouter-compatible runtime and the anthropic runtime, with catalog and wire tests covering the new fields.
Closes #1520.