Skip to content

fix(config): honor the http: YAML block for upstream client timeouts#483

Merged
SantiagoDePolonia merged 1 commit into
mainfrom
refactor/config-escape-hatches
Jul 5, 2026
Merged

fix(config): honor the http: YAML block for upstream client timeouts#483
SantiagoDePolonia merged 1 commit into
mainfrom
refactor/config-escape-hatches

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Closing out the architecture review's config escape-hatch item — with a twist found during verification.

The real defect (fixed)

config.HTTPConfig (http: YAML block) was parsed, defaulted, and documented in config.example.yaml — but no code ever read it. Setting http.timeout / http.response_header_timeout in YAML was silently ignored; only the HTTP_TIMEOUT / HTTP_RESPONSE_HEADER_TIMEOUT env vars worked, via httpclient's direct env read.

Fix: httpclient.SetConfiguredTimeouts installs the YAML values at app startup, before any provider constructs a transport. Precedence (highest first): env var → YAML http: block → built-in 600s default, matching the project-wide env-over-YAML convention. Pinned by a test covering all three tiers plus clearing.

Findings re-verified, not blindly applied

  • The review claimed OPENROUTER_SITE_URL/OPENROUTER_APP_NAME were "undocumented hidden config" — false positive: both are in .env.template and the provider docs. The review doc is corrected in place.
  • The remaining per-provider construction-time env reads (openrouter, opencodego) are kept deliberately — that's the established quirk-knob convention, and plumbing them through ProviderConfig would add ceremony for no behavior gain.
  • The anthropic request-time ANTHROPIC_DEFAULT_MAX_TOKENS read keeps its warn-once + safe-fallback behavior; threading it through provider construction wasn't worth the signature churn. Recorded in the review doc.
  • CLAUDE.md's provider list gains the missing mentions (OPENROUTER_SITE_URL/APP_NAME, ANTHROPIC_DEFAULT_MAX_TOKENS) and the HTTP bullet now notes the YAML block.

User-visible impact

YAML-configured HTTP timeouts now actually apply. Anyone relying on the documented http: block gets the behavior the docs always promised; env-var users see no change.

Testing

Full go build ./... and go test ./...; new precedence test in internal/httpclient; golangci-lint 0 issues; pre-commit make test-race on the commit.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • HTTP client timeout settings from the app http: configuration are now applied during startup.
    • Environment variables still override configuration, and cleared/non-positive values fall back to built-in defaults.
  • Documentation
    • Updated HTTP timeout and provider environment-variable documentation to clarify precedence and added/clarified provider attribution and token-limit settings.
  • Tests
    • Added tests covering timeout precedence across built-in defaults, configured values, and environment overrides.

@mintlify

mintlify Bot commented Jul 5, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
gomodel 🟢 Ready View Preview Jul 5, 2026, 9:17 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5b18cf74-a6c9-48a6-b4e3-3c8f7cc85c28

📥 Commits

Reviewing files that changed from the base of the PR and between 2b33a3a and 4615b8f.

📒 Files selected for processing (6)
  • CLAUDE.md
  • config/http.go
  • docs/dev/2026-07-04_architecture-review.md
  • internal/app/app.go
  • internal/httpclient/client.go
  • internal/httpclient/client_test.go

📝 Walkthrough

Walkthrough

Adds startup installation of YAML-configured HTTP timeouts into internal/httpclient, keeps environment variables higher precedence, and updates the related comments, tests, and documentation.

Changes

HTTP Timeout Configuration Wiring

Layer / File(s) Summary
httpclient configured timeout precedence
internal/httpclient/client.go, internal/httpclient/client_test.go
Adds atomic-backed configured timeout defaults, exported SetConfiguredTimeouts, updated DefaultConfig() precedence, and a test covering env/config/default resolution.
App startup wiring
internal/app/app.go, config/http.go
Imports internal/httpclient, installs appCfg.HTTP timeout values during startup, and updates the HTTPConfig comment to describe the precedence.
Documentation updates
CLAUDE.md, docs/dev/2026-07-04_architecture-review.md
Updates HTTP client and provider env-var docs and records the fixed note about YAML HTTP timeouts now being applied at startup.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant App as app.New
  participant Config as appCfg.HTTP
  participant HTTPClient as httpclient.SetConfiguredTimeouts
  participant Providers as provider construction

  App->>Config: read Timeout and ResponseHeaderTimeout
  App->>HTTPClient: SetConfiguredTimeouts(timeout, responseHeaderTimeout)
  App->>Providers: construct providers
  Providers->>HTTPClient: DefaultConfig()
  HTTPClient-->>Providers: env vars, configured values, or built-in defaults
Loading

Poem

A rabbit found a timeout trail,
With YAML paths and env vars in the mail.
Set at startup, then off we go,
With precedence rules that clearly show. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main fix: applying the http YAML block to upstream client timeouts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/config-escape-hatches

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

Safe to merge with minimal risk.

The change is focused and installs the YAML timeout defaults before provider clients are constructed. Existing env-var override behavior is preserved. Tests cover the relevant precedence paths.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Inspected the contract-validation proof log to confirm that internal/app tests ran and exited with code 0, and that the go build for cmd/gomodel exited with code 0.

View all artifacts

T-Rex Ran code and verified through T-Rex

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Loader as config.Load
participant App as internal/app.New
participant HTTP as internal/httpclient
participant Providers as providers.Init
participant Client as Upstream HTTP client

Loader->>App: Loaded config with http.timeout values
App->>HTTP: SetConfiguredTimeouts(timeout, responseHeaderTimeout)
App->>Providers: Init providers after timeout install
Providers->>HTTP: NewDefaultHTTPClient()
HTTP->>HTTP: "DefaultConfig(): env var -> YAML timeout -> 600s default"
HTTP-->>Client: http.Client with resolved timeouts
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Loader as config.Load
participant App as internal/app.New
participant HTTP as internal/httpclient
participant Providers as providers.Init
participant Client as Upstream HTTP client

Loader->>App: Loaded config with http.timeout values
App->>HTTP: SetConfiguredTimeouts(timeout, responseHeaderTimeout)
App->>Providers: Init providers after timeout install
Providers->>HTTP: NewDefaultHTTPClient()
HTTP->>HTTP: "DefaultConfig(): env var -> YAML timeout -> 600s default"
HTTP-->>Client: http.Client with resolved timeouts
Loading

Reviews (1): Last reviewed commit: "fix(config): honor the http: YAML block ..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/httpclient/client_test.go`:
- Around line 291-297: The “clear back to default” test case in DefaultConfig
only verifies Timeout and leaves ResponseHeaderTimeout unchecked, so add a
symmetric assertion for the response header timeout reset as well. Update the
final scenario around SetConfiguredTimeouts and DefaultConfig to confirm
configuredResponseHeaderTimeoutSeconds reverts to the built-in default when the
env value is cleared, using the existing DefaultConfig() and
ResponseHeaderTimeout symbols to locate the coverage gap.

In `@internal/httpclient/client.go`:
- Around line 71-81: Clarify the timeout behavior in
SetConfiguredTimeouts/configuredOrDefault: non-positive YAML-configured values
are currently treated as “unset” and fall back to the default instead of
disabling the timeout like the env-var path can. Update the
SetConfiguredTimeouts docstring (and any nearby comments on configuredOrDefault
if needed) to explicitly state that 0 or negative values do not mean “no
timeout” here and will revert to the built-in defaults, so callers understand
the asymmetry with getEnvDuration and http.Client.Timeout.
- Around line 90-100: `DefaultConfig()` and `config.Load()` are parsing
`HTTP_TIMEOUT` differently, so the env var works in
`internal/httpclient.getEnvDuration()` but fails when
`config.HTTPConfig.Timeout` is loaded as an int. Update the config-loading path
to use the same duration parsing as `DefaultConfig()` (or make both paths
seconds-only), and keep the `HTTP_TIMEOUT` handling consistent across the
`ClientConfig` and `HTTPConfig.Timeout` code paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7e939de3-8745-4f24-ba23-5d21b2d6aadc

📥 Commits

Reviewing files that changed from the base of the PR and between 72246d5 and 2b33a3a.

📒 Files selected for processing (6)
  • CLAUDE.md
  • config/http.go
  • docs/dev/2026-07-04_architecture-review.md
  • internal/app/app.go
  • internal/httpclient/client.go
  • internal/httpclient/client_test.go

Comment on lines +291 to +297
// Non-positive values clear back to the built-in default.
t.Setenv("HTTP_TIMEOUT", "")
SetConfiguredTimeouts(-1, 0)
if got := DefaultConfig().Timeout; got != 600*time.Second {
t.Fatalf("cleared Timeout = %v, want 600s", got)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Final scenario doesn't assert ResponseHeaderTimeout reverts too.

The "clear back to default" case only checks .Timeout; HTTP_RESPONSE_HEADER_TIMEOUT remains set to "60" from the earlier scenario and configuredResponseHeaderTimeoutSeconds isn't independently verified to revert. Consider asserting ResponseHeaderTimeout as well for symmetric coverage.

✅ Suggested addition
 	// Non-positive values clear back to the built-in default.
 	t.Setenv("HTTP_TIMEOUT", "")
+	t.Setenv("HTTP_RESPONSE_HEADER_TIMEOUT", "")
 	SetConfiguredTimeouts(-1, 0)
-	if got := DefaultConfig().Timeout; got != 600*time.Second {
-		t.Fatalf("cleared Timeout = %v, want 600s", got)
-	}
+	cfg = DefaultConfig()
+	if cfg.Timeout != 600*time.Second {
+		t.Fatalf("cleared Timeout = %v, want 600s", cfg.Timeout)
+	}
+	if cfg.ResponseHeaderTimeout != 600*time.Second {
+		t.Fatalf("cleared ResponseHeaderTimeout = %v, want 600s", cfg.ResponseHeaderTimeout)
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/httpclient/client_test.go` around lines 291 - 297, The “clear back
to default” test case in DefaultConfig only verifies Timeout and leaves
ResponseHeaderTimeout unchecked, so add a symmetric assertion for the response
header timeout reset as well. Update the final scenario around
SetConfiguredTimeouts and DefaultConfig to confirm
configuredResponseHeaderTimeoutSeconds reverts to the built-in default when the
env value is cleared, using the existing DefaultConfig() and
ResponseHeaderTimeout symbols to locate the coverage gap.

Comment thread internal/httpclient/client.go
Comment on lines 90 to +100
func DefaultConfig() ClientConfig {
defaultLongTimeout := 600 * time.Second
return ClientConfig{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
Timeout: getEnvDuration("HTTP_TIMEOUT", defaultLongTimeout),
Timeout: getEnvDuration("HTTP_TIMEOUT", configuredOrDefault(&configuredTimeoutSeconds, defaultLongTimeout)),
DialTimeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: getEnvDuration("HTTP_RESPONSE_HEADER_TIMEOUT", defaultLongTimeout),
ResponseHeaderTimeout: getEnvDuration("HTTP_RESPONSE_HEADER_TIMEOUT", configuredOrDefault(&configuredResponseHeaderTimeoutSeconds, defaultLongTimeout)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect how config.Load() applies env overrides for HTTPConfig fields.
rg -n "applyEnvOverrides" -A 40 config/*.go
rg -n "HTTP_TIMEOUT|HTTP_RESPONSE_HEADER_TIMEOUT" config/*.go
rg -n "env:\"" config/http.go

Repository: ENTERPILOT/GoModel

Length of output: 10854


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== internal/httpclient/client.go =="
sed -n '1,220p' internal/httpclient/client.go

echo
echo "== config/http.go =="
cat -n config/http.go

echo
echo "== config/env.go around int parsing =="
sed -n '80,140p' config/env.go

echo
echo "== tests touching HTTP_TIMEOUT =="
sed -n '250,330p' config/config_helpers_test.go
sed -n '1390,1435p' config/config_test.go

Repository: ENTERPILOT/GoModel

Length of output: 11805


Align HTTP_TIMEOUT parsing in both config paths

config.Load() still parses config.HTTPConfig.Timeout as an int, so HTTP_TIMEOUT=2m fails during config load even though internal/httpclient.getEnvDuration() accepts Go duration strings. Make the two paths accept the same format, or document these env vars as seconds-only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/httpclient/client.go` around lines 90 - 100, `DefaultConfig()` and
`config.Load()` are parsing `HTTP_TIMEOUT` differently, so the env var works in
`internal/httpclient.getEnvDuration()` but fails when
`config.HTTPConfig.Timeout` is loaded as an int. Update the config-loading path
to use the same duration parsing as `DefaultConfig()` (or make both paths
seconds-only), and keep the `HTTP_TIMEOUT` handling consistent across the
`ClientConfig` and `HTTPConfig.Timeout` code paths.

@SantiagoDePolonia

Copy link
Copy Markdown
Contributor Author

@copilot Resolve conflicts in CLAUDE.md - merging main

config.HTTPConfig was parsed, defaulted, and documented in
config.example.yaml but never read by any code: setting http.timeout or
http.response_header_timeout in YAML was silently ignored, and only the
HTTP_TIMEOUT / HTTP_RESPONSE_HEADER_TIMEOUT env vars worked via httpclient's
direct read. App startup now installs the YAML values into httpclient before
any provider constructs a transport; env vars keep precedence per the
project-wide env-over-YAML convention. Precedence is pinned by a test.

Also from verifying the review's config escape-hatch findings: the
OPENROUTER_SITE_URL/APP_NAME 'undocumented hidden config' claim was a false
positive (both are in .env.template and provider docs) — the review doc is
corrected, and CLAUDE.md's provider list gains the missing mentions
(OPENROUTER_SITE_URL/APP_NAME, ANTHROPIC_DEFAULT_MAX_TOKENS). The remaining
per-provider construction-time env reads are kept deliberately as the
established quirk-knob convention; the anthropic request-time read keeps its
warn-once fallback (plumbing judged not worth the churn), both recorded in
the review doc.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 90.00000% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/app/app.go 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@SantiagoDePolonia SantiagoDePolonia merged commit 54b3429 into main Jul 5, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants