From dc018739a445f21e591b029e61dc456f9e4aba70 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 10 Mar 2026 20:38:53 -0700 Subject: [PATCH 1/2] Add tls_skip_verify option for corporate/internal CAs Allows skipping TLS certificate verification on the shared HTTP client, needed when openai_base_url points to an endpoint behind an internal CA (e.g. litellm proxy). Configurable via config.yaml or TLS_SKIP_VERIFY env. --- config.yaml | 2 ++ internal/app/app.go | 2 +- internal/config/config.go | 4 +++- internal/httpx/http_client.go | 8 +++++++- internal/httpx/http_client_test.go | 4 ++-- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/config.yaml b/config.yaml index d043a32..54178ec 100644 --- a/config.yaml +++ b/config.yaml @@ -45,6 +45,8 @@ openai_base_url: "https://api.openai.com/v1" db_path: "./reportbot.db" report_output_dir: "./reportbot-reports" external_http_timeout_seconds: 90 +# Skip TLS certificate verification (for internal/corporate CAs) +tls_skip_verify: false # Channel ID used for report reminders and links report_channel_id: "C01234567" diff --git a/internal/app/app.go b/internal/app/app.go index 3079833..dcc41f3 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -15,7 +15,7 @@ import ( func Main() { cfg := config.LoadConfig() - appliedHTTPTimeout := httpx.ConfigureExternalHTTPClient(cfg.ExternalHTTPTimeoutSeconds) + appliedHTTPTimeout := httpx.ConfigureExternalHTTPClient(cfg.ExternalHTTPTimeoutSeconds, cfg.TLSSkipVerify) log.Printf( "Config loaded. Team=%s Managers=%d TeamMembers=%d Timezone=%s LLMBatchSize=%d LLMConfidenceThreshold=%.2f LLMExampleCount=%d LLMExampleMaxChars=%d LLMGlossaryPath=%s OpenAIBaseURL=%s ExternalHTTPTimeout=%s", cfg.TeamName, diff --git a/internal/config/config.go b/internal/config/config.go index f42d2ea..58c676d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -45,7 +45,8 @@ type Config struct { DBPath string `yaml:"db_path"` ReportOutputDir string `yaml:"report_output_dir"` ReportChannelID string `yaml:"report_channel_id"` - ExternalHTTPTimeoutSeconds int `yaml:"external_http_timeout_seconds"` + ExternalHTTPTimeoutSeconds int `yaml:"external_http_timeout_seconds"` + TLSSkipVerify bool `yaml:"tls_skip_verify"` ManagerSlackIDs []string `yaml:"manager_slack_ids"` TeamMembers []string `yaml:"team_members"` @@ -107,6 +108,7 @@ func LoadConfig() Config { envOverride(&cfg.ReportOutputDir, "REPORT_OUTPUT_DIR") envOverride(&cfg.ReportChannelID, "REPORT_CHANNEL_ID") envOverrideInt(&cfg.ExternalHTTPTimeoutSeconds, "EXTERNAL_HTTP_TIMEOUT_SECONDS") + envOverrideBool(&cfg.TLSSkipVerify, "TLS_SKIP_VERIFY") envOverride(&cfg.TeamName, "TEAM_NAME") envOverride(&cfg.NudgeDay, "NUDGE_DAY") envOverride(&cfg.NudgeTime, "NUDGE_TIME") diff --git a/internal/httpx/http_client.go b/internal/httpx/http_client.go index 541da6c..08bc1c8 100644 --- a/internal/httpx/http_client.go +++ b/internal/httpx/http_client.go @@ -1,6 +1,7 @@ package httpx import ( + "crypto/tls" "net/http" "time" ) @@ -11,12 +12,17 @@ var externalHTTPClient = &http.Client{ Timeout: defaultExternalHTTPTimeout, } -func ConfigureExternalHTTPClient(timeoutSeconds int) time.Duration { +func ConfigureExternalHTTPClient(timeoutSeconds int, tlsSkipVerify bool) time.Duration { timeout := defaultExternalHTTPTimeout if timeoutSeconds > 0 { timeout = time.Duration(timeoutSeconds) * time.Second } externalHTTPClient.Timeout = timeout + if tlsSkipVerify { + externalHTTPClient.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + } return timeout } diff --git a/internal/httpx/http_client_test.go b/internal/httpx/http_client_test.go index 3d4fe60..1e9c80a 100644 --- a/internal/httpx/http_client_test.go +++ b/internal/httpx/http_client_test.go @@ -23,7 +23,7 @@ func TestConfigureExternalHTTPClient(t *testing.T) { externalHTTPClient.Timeout = original }) - got := ConfigureExternalHTTPClient(0) + got := ConfigureExternalHTTPClient(0, false) if got != defaultExternalHTTPTimeout { t.Fatalf("ConfigureExternalHTTPClient(0) = %s, want %s", got, defaultExternalHTTPTimeout) } @@ -31,7 +31,7 @@ func TestConfigureExternalHTTPClient(t *testing.T) { t.Fatalf("configured timeout = %s, want %s", externalHTTPClient.Timeout, defaultExternalHTTPTimeout) } - got = ConfigureExternalHTTPClient(120) + got = ConfigureExternalHTTPClient(120, false) if got != 120*time.Second { t.Fatalf("ConfigureExternalHTTPClient(120) = %s, want %s", got, 120*time.Second) } From 198f08da75d86dd3d5e77719c90e001a5aed4a1e Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 10 Mar 2026 21:08:42 -0700 Subject: [PATCH 2/2] Log raw response body on OpenAI Responses API errors Adds HTTP status check before JSON parsing and includes truncated response body in error messages to aid debugging non-JSON responses from proxied endpoints. --- internal/integrations/llm/llm.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/internal/integrations/llm/llm.go b/internal/integrations/llm/llm.go index 5937c64..a9656f6 100644 --- a/internal/integrations/llm/llm.go +++ b/internal/integrations/llm/llm.go @@ -703,9 +703,23 @@ func doOpenAIResponsesRequest(apiKey, baseURL string, reqBody openAIResponsesReq return "", LLMUsage{}, fmt.Errorf("reading responses body: %w", err) } + if resp.StatusCode != http.StatusOK { + truncated := string(respBody) + if len(truncated) > 512 { + truncated = truncated[:512] + "..." + } + log.Printf("llm openai responses HTTP %d: %s", resp.StatusCode, truncated) + return "", LLMUsage{}, fmt.Errorf("OpenAI Responses API HTTP %d: %s", resp.StatusCode, truncated) + } + var responsesResp openAIResponsesResponse if err := json.Unmarshal(respBody, &responsesResp); err != nil { - return "", LLMUsage{}, fmt.Errorf("parsing OpenAI Responses payload: %w", err) + truncated := string(respBody) + if len(truncated) > 512 { + truncated = truncated[:512] + "..." + } + log.Printf("llm openai responses invalid JSON: %s", truncated) + return "", LLMUsage{}, fmt.Errorf("parsing OpenAI Responses payload: %w (body: %s)", err, truncated) } if responsesResp.Error != nil { log.Printf("llm openai responses api error: %s", responsesResp.Error.Message)