Description of the Change
Problem
GetHTTPClient builds its own http.Transport with only TLS (and proxy) settings, so it has no dial timeout and no TLS handshake timeout. When a host never answers, each attempt waits for the OS SYN timeout (about 2m10s on Linux). Flow setup retries 3 times through retryTransient, which logs nothing per attempt, so a flow with an unreachable LLM endpoint sits silently for about 7 minutes. HTTP_CLIENT_TIMEOUT (#205) caps the whole request but doesn't help with this.
Solution
pkg/system/utils.go: both transports get anet.DialerwithTimeout: 30s, KeepAlive: 30sandTLSHandshakeTimeout: 10s, the same values ashttp.DefaultTransportand the reverse proxy inpkg/server/router.go. Proxy and TLS settings are unchanged.- No
ResponseHeaderTimeoutand no change toClient.Timeout: slow reasoning answers are still bounded only byHTTP_CLIENT_TIMEOUT. pkg/providers/retry.go:retryTransientlogs one warning per failed attempt with the attempt number and the error. Deterministic errors and cancellation return as before.
Closes #460
Type of Change
- 🐛 Bug fix (non-breaking change which fixes an issue)
Areas Affected
- External Integrations (LLM/Search APIs)
Testing and Verification
Test Configuration
PentAGI Version: 2.2.0-ce (843dfeb) Docker Version: 29.9.1 Host OS: Linux LLM Provider: OpenAI (unreachable endpoint, no requests reach the API) Enabled Features: none
Test Steps
go test ./pkg/system/ ./pkg/providers/ -racego vet,golangci-lint run ./pkg/system/... ./pkg/providers/...- Live: point
OPEN_AI_SERVER_URLat a host that drops SYNs and create a flow, with and without the patch.
Test Results
- Unit tests (no network needed; the unreachable host is a loopback listener with a full accept queue, so Linux drops the SYNs):
- an unreachable endpoint and an unreachable proxy fail within the dial timeout
- a server that accepts but never answers the TLS handshake fails within the handshake timeout
- a slow answer is not cut off, and requests still go through the configured proxy
- the transports keep the CA pool,
InsecureSkipVerify, the proxy, andClient.Timeout; there is no response header timeout - cancel during the dial returns at once
retryTransientlogs once per failed attempt, and not for deterministic errors or cancellation- The connect and log tests fail on the current code.
- Setup call against an unreachable host:
| per attempt | total (3 attempts) | logs during the wait | |
|---|---|---|---|
| before | 2m14s | 6m53s | none |
| after | 30s | 1m40s | one warning per attempt |
- Live flow: before, the flow failed after about 7 minutes with only the final error. After, it failed at 1m40s with three
call failedwarnings first. - With real defaults: a server that answers after 60s still succeeds, and
HTTP_CLIENT_TIMEOUT=2still cuts off a 5s answer.
Security Considerations
None. TLS verification and the custom CA pool are unchanged.
Performance Impact
None for reachable hosts. An unreachable host now fails each attempt in 30s instead of about 2 minutes.
Documentation Updates
None.
Deployment Notes
None. No new settings.
Checklist
- I have added tests to cover my changes
- All new and existing tests pass
- I have run
go fmtandgo vet(for Go code) - Changes are backward compatible
Additional Notes
The bounds are fixed values, like in router.go. If you'd rather make the connect timeout configurable next to HTTP_CLIENT_TIMEOUT, I can add that.
HUMAN:
My very first PentAGI flow sat for about 7 minutes before failing, and docker logs pentagi showed nothing the whole time. It turned out my containers had no network, and the shared HTTP client has no connect timeout, so each of the 3 setup attempts waited on the OS's roughly 2-minute TCP timeout. I added a 30s connect timeout and a 10s TLS handshake timeout, the same values router.go already uses, plus a warning for each retry. I left response time unbounded on purpose, because slow reasoning calls are legitimate. If you'd like the timeouts configurable next to HTTP_CLIENT_TIMEOUT, I'm happy to add that.