From 943385105fc432ec7e09c75aedb16d77050bc788 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Thu, 5 Mar 2026 20:56:38 +0900 Subject: [PATCH 1/7] fix: handle ignored io.ReadAll errors across codebase io.ReadAll errors were silently discarded with `body, _ := io.ReadAll(...)`, which could cause empty or partial data to be used for JSON unmarshaling or error messages. This adds proper error checks for all instances. --- .../internal/ui/model.go | 6 ++++- .../internal/server/auth_handlers.go | 5 +++- cmd/picoclaw/internal/auth/helpers.go | 5 +++- pkg/auth/oauth.go | 25 +++++++++++++++---- pkg/channels/line/line.go | 5 +++- pkg/channels/wecom/aibot.go | 5 +++- pkg/channels/wecom/app.go | 10 ++++++-- pkg/channels/wecom/bot.go | 5 +++- pkg/providers/antigravity_provider.go | 10 ++++++-- 9 files changed, 61 insertions(+), 15 deletions(-) diff --git a/cmd/picoclaw-launcher-tui/internal/ui/model.go b/cmd/picoclaw-launcher-tui/internal/ui/model.go index ba91f5b09..304b4efa7 100644 --- a/cmd/picoclaw-launcher-tui/internal/ui/model.go +++ b/cmd/picoclaw-launcher-tui/internal/ui/model.go @@ -335,7 +335,11 @@ func (s *appState) testModel(model *picoclawconfig.ModelConfig) { s.showMessage("Test OK", resp.Status) return } - body, _ := io.ReadAll(io.LimitReader(resp.Body, 2048)) + body, err := io.ReadAll(io.LimitReader(resp.Body, 2048)) + if err != nil { + s.showMessage("Test failed", fmt.Sprintf("failed to read response: %v", err)) + return + } s.showMessage( "Test failed", fmt.Sprintf("%s: %s", resp.Status, strings.TrimSpace(string(body))), diff --git a/cmd/picoclaw-launcher/internal/server/auth_handlers.go b/cmd/picoclaw-launcher/internal/server/auth_handlers.go index 1e9b8be0a..3b48f9739 100644 --- a/cmd/picoclaw-launcher/internal/server/auth_handlers.go +++ b/cmd/picoclaw-launcher/internal/server/auth_handlers.go @@ -297,7 +297,10 @@ func fetchGoogleUserEmail(accessToken string) (string, error) { } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("reading userinfo response: %w", err) + } if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("userinfo request failed: %s", string(body)) } diff --git a/cmd/picoclaw/internal/auth/helpers.go b/cmd/picoclaw/internal/auth/helpers.go index 633ce8740..4dfbc92e7 100644 --- a/cmd/picoclaw/internal/auth/helpers.go +++ b/cmd/picoclaw/internal/auth/helpers.go @@ -177,7 +177,10 @@ func fetchGoogleUserEmail(accessToken string) (string, error) { } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("reading userinfo response: %w", err) + } if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("userinfo request failed: %s", string(body)) } diff --git a/pkg/auth/oauth.go b/pkg/auth/oauth.go index 91c9e25c5..4667e3d81 100644 --- a/pkg/auth/oauth.go +++ b/pkg/auth/oauth.go @@ -212,7 +212,10 @@ func RequestDeviceCode(cfg OAuthProviderConfig) (*DeviceCodeInfo, error) { } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("reading device code response: %w", err) + } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("device code request failed: %s", string(body)) } @@ -300,7 +303,10 @@ func LoginDeviceCode(cfg OAuthProviderConfig) (*AuthCredential, error) { } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("reading device code response: %w", err) + } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("device code request failed: %s", string(body)) } @@ -360,7 +366,10 @@ func pollDeviceCode(cfg OAuthProviderConfig, deviceAuthID, userCode string) (*Au return nil, fmt.Errorf("pending") } - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("reading device token response: %w", err) + } var tokenResp struct { AuthorizationCode string `json:"authorization_code"` @@ -401,7 +410,10 @@ func RefreshAccessToken(cred *AuthCredential, cfg OAuthProviderConfig) (*AuthCre } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("reading token refresh response: %w", err) + } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("token refresh failed: %s", string(body)) } @@ -494,7 +506,10 @@ func ExchangeCodeForTokens(cfg OAuthProviderConfig, code, codeVerifier, redirect } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("reading token exchange response: %w", err) + } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("token exchange failed: %s", string(body)) } diff --git a/pkg/channels/line/line.go b/pkg/channels/line/line.go index 398f12e6b..d0badc1f3 100644 --- a/pkg/channels/line/line.go +++ b/pkg/channels/line/line.go @@ -654,7 +654,10 @@ func (c *LINEChannel) callAPI(ctx context.Context, endpoint string, payload any) defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - respBody, _ := io.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("reading LINE API error response: %w", err) + } return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("LINE API error: %s", string(respBody))) } diff --git a/pkg/channels/wecom/aibot.go b/pkg/channels/wecom/aibot.go index 6c5aca40b..93fe8c36d 100644 --- a/pkg/channels/wecom/aibot.go +++ b/pkg/channels/wecom/aibot.go @@ -793,7 +793,10 @@ func (c *WeComAIBotChannel) sendViaResponseURL(responseURL, content string) erro return nil } - respBody, _ := io.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("reading response_url body: %w: %w", channels.ErrTemporary, err) + } switch { case resp.StatusCode == http.StatusTooManyRequests: return fmt.Errorf("response_url rate limited (%d): %s: %w", diff --git a/pkg/channels/wecom/app.go b/pkg/channels/wecom/app.go index 717815b9f..c1aa9640f 100644 --- a/pkg/channels/wecom/app.go +++ b/pkg/channels/wecom/app.go @@ -321,7 +321,10 @@ func (c *WeComAppChannel) uploadMedia(ctx context.Context, accessToken, mediaTyp defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - respBody, _ := io.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("reading wecom upload error response: %w", err) + } return "", channels.ClassifySendError(resp.StatusCode, fmt.Errorf("wecom upload error: %s", string(respBody))) } @@ -371,7 +374,10 @@ func (c *WeComAppChannel) sendWeComMessage(ctx context.Context, accessToken stri defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - respBody, _ := io.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("reading wecom_app error response: %w", err) + } return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("wecom_app API error: %s", string(respBody))) } diff --git a/pkg/channels/wecom/bot.go b/pkg/channels/wecom/bot.go index 9126a847d..3740bcd41 100644 --- a/pkg/channels/wecom/bot.go +++ b/pkg/channels/wecom/bot.go @@ -453,7 +453,10 @@ func (c *WeComBotChannel) sendWebhookReply(ctx context.Context, userID, content defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("reading webhook error response: %w", err) + } return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("webhook API error: %s", string(body))) } diff --git a/pkg/providers/antigravity_provider.go b/pkg/providers/antigravity_provider.go index d4ee528b7..8a1890212 100644 --- a/pkg/providers/antigravity_provider.go +++ b/pkg/providers/antigravity_provider.go @@ -640,7 +640,10 @@ func FetchAntigravityProjectID(accessToken string) (string, error) { } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("reading loadCodeAssist response: %w", err) + } if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("loadCodeAssist failed: %s", string(body)) } @@ -681,7 +684,10 @@ func FetchAntigravityModels(accessToken, projectID string) ([]AntigravityModelIn } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("reading fetchAvailableModels response: %w", err) + } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf( "fetchAvailableModels failed (HTTP %d): %s", From 8d2f2d67b2d57ad30f35df78b3ad612e69f42a08 Mon Sep 17 00:00:00 2001 From: mattn Date: Thu, 5 Mar 2026 21:20:20 +0900 Subject: [PATCH 2/7] Update pkg/channels/line/line.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/channels/line/line.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/channels/line/line.go b/pkg/channels/line/line.go index d0badc1f3..b36350a06 100644 --- a/pkg/channels/line/line.go +++ b/pkg/channels/line/line.go @@ -656,7 +656,7 @@ func (c *LINEChannel) callAPI(ctx context.Context, endpoint string, payload any) if resp.StatusCode != http.StatusOK { respBody, err := io.ReadAll(resp.Body) if err != nil { - return fmt.Errorf("reading LINE API error response: %w", err) + return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("reading LINE API error response: %w", err)) } return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("LINE API error: %s", string(respBody))) } From ca4e44bd0feef193ae12dbb4bf53b731639ad52a Mon Sep 17 00:00:00 2001 From: mattn Date: Thu, 5 Mar 2026 21:20:31 +0900 Subject: [PATCH 3/7] Update pkg/channels/wecom/bot.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/channels/wecom/bot.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/channels/wecom/bot.go b/pkg/channels/wecom/bot.go index 3740bcd41..0e281ff2f 100644 --- a/pkg/channels/wecom/bot.go +++ b/pkg/channels/wecom/bot.go @@ -455,7 +455,7 @@ func (c *WeComBotChannel) sendWebhookReply(ctx context.Context, userID, content if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { - return fmt.Errorf("reading webhook error response: %w", err) + return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("reading webhook error response: %w", err)) } return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("webhook API error: %s", string(body))) } From ee2ebc8bf35141abc609457c92964baf73c08335 Mon Sep 17 00:00:00 2001 From: mattn Date: Thu, 5 Mar 2026 21:20:40 +0900 Subject: [PATCH 4/7] Update pkg/channels/wecom/app.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/channels/wecom/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/channels/wecom/app.go b/pkg/channels/wecom/app.go index c1aa9640f..1759abaa3 100644 --- a/pkg/channels/wecom/app.go +++ b/pkg/channels/wecom/app.go @@ -323,7 +323,7 @@ func (c *WeComAppChannel) uploadMedia(ctx context.Context, accessToken, mediaTyp if resp.StatusCode != http.StatusOK { respBody, err := io.ReadAll(resp.Body) if err != nil { - return "", fmt.Errorf("reading wecom upload error response: %w", err) + return "", channels.ClassifySendError(resp.StatusCode, fmt.Errorf("reading wecom upload error response: %w", err)) } return "", channels.ClassifySendError(resp.StatusCode, fmt.Errorf("wecom upload error: %s", string(respBody))) } From 42a32fbf3bb956340e59985f2d56f36fde4db1d8 Mon Sep 17 00:00:00 2001 From: mattn Date: Thu, 5 Mar 2026 21:20:51 +0900 Subject: [PATCH 5/7] Update pkg/channels/wecom/app.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/channels/wecom/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/channels/wecom/app.go b/pkg/channels/wecom/app.go index 1759abaa3..bca47ea8e 100644 --- a/pkg/channels/wecom/app.go +++ b/pkg/channels/wecom/app.go @@ -376,7 +376,7 @@ func (c *WeComAppChannel) sendWeComMessage(ctx context.Context, accessToken stri if resp.StatusCode != http.StatusOK { respBody, err := io.ReadAll(resp.Body) if err != nil { - return fmt.Errorf("reading wecom_app error response: %w", err) + return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("reading wecom_app error response: %w", err)) } return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("wecom_app API error: %s", string(respBody))) } From b8782729623e55ab09a985259fd46c5c7ee0d95a Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Thu, 5 Mar 2026 21:54:13 +0900 Subject: [PATCH 6/7] fix: resolve govet shadow and golines lint errors in wecom channels --- pkg/channels/wecom/app.go | 24 ++++++++++++++++++++++-- pkg/channels/wecom/bot.go | 11 ++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/pkg/channels/wecom/app.go b/pkg/channels/wecom/app.go index bca47ea8e..b86f7ae2b 100644 --- a/pkg/channels/wecom/app.go +++ b/pkg/channels/wecom/app.go @@ -321,11 +321,22 @@ func (c *WeComAppChannel) uploadMedia(ctx context.Context, accessToken, mediaTyp defer resp.Body.Close() if resp.StatusCode != http.StatusOK { +<<<<<<< HEAD respBody, err := io.ReadAll(resp.Body) if err != nil { return "", channels.ClassifySendError(resp.StatusCode, fmt.Errorf("reading wecom upload error response: %w", err)) +======= + respBody, readErr := io.ReadAll(resp.Body) + if readErr != nil { + return "", fmt.Errorf( + "reading wecom upload error response: %w", readErr, + ) +>>>>>>> 908fa8d (fix: resolve govet shadow and golines lint errors in wecom channels) } - return "", channels.ClassifySendError(resp.StatusCode, fmt.Errorf("wecom upload error: %s", string(respBody))) + return "", channels.ClassifySendError( + resp.StatusCode, + fmt.Errorf("wecom upload error: %s", string(respBody)), + ) } var result struct { @@ -374,11 +385,20 @@ func (c *WeComAppChannel) sendWeComMessage(ctx context.Context, accessToken stri defer resp.Body.Close() if resp.StatusCode != http.StatusOK { +<<<<<<< HEAD respBody, err := io.ReadAll(resp.Body) if err != nil { return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("reading wecom_app error response: %w", err)) +======= + respBody, readErr := io.ReadAll(resp.Body) + if readErr != nil { + return fmt.Errorf("reading wecom_app error response: %w", readErr) +>>>>>>> 908fa8d (fix: resolve govet shadow and golines lint errors in wecom channels) } - return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("wecom_app API error: %s", string(respBody))) + return channels.ClassifySendError( + resp.StatusCode, + fmt.Errorf("wecom_app API error: %s", string(respBody)), + ) } respBody, err := io.ReadAll(resp.Body) diff --git a/pkg/channels/wecom/bot.go b/pkg/channels/wecom/bot.go index 0e281ff2f..8d64a91c6 100644 --- a/pkg/channels/wecom/bot.go +++ b/pkg/channels/wecom/bot.go @@ -453,11 +453,20 @@ func (c *WeComBotChannel) sendWebhookReply(ctx context.Context, userID, content defer resp.Body.Close() if resp.StatusCode != http.StatusOK { +<<<<<<< HEAD body, err := io.ReadAll(resp.Body) if err != nil { return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("reading webhook error response: %w", err)) +======= + body, readErr := io.ReadAll(resp.Body) + if readErr != nil { + return fmt.Errorf("reading webhook error response: %w", readErr) +>>>>>>> 908fa8d (fix: resolve govet shadow and golines lint errors in wecom channels) } - return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("webhook API error: %s", string(body))) + return channels.ClassifySendError( + resp.StatusCode, + fmt.Errorf("webhook API error: %s", string(body)), + ) } body, err := io.ReadAll(resp.Body) From 03d6ad420f573b4eff138c561ef79d564f3eeef1 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Thu, 5 Mar 2026 22:01:32 +0900 Subject: [PATCH 7/7] fix: resolve merge conflicts in wecom error handling Combine both shadow variable fix (readErr) and proper error classification (ClassifySendError) in wecom app and bot channels. --- pkg/channels/wecom/app.go | 22 +++++++--------------- pkg/channels/wecom/bot.go | 11 ++++------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/pkg/channels/wecom/app.go b/pkg/channels/wecom/app.go index b86f7ae2b..2098fcd4e 100644 --- a/pkg/channels/wecom/app.go +++ b/pkg/channels/wecom/app.go @@ -321,17 +321,12 @@ func (c *WeComAppChannel) uploadMedia(ctx context.Context, accessToken, mediaTyp defer resp.Body.Close() if resp.StatusCode != http.StatusOK { -<<<<<<< HEAD - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return "", channels.ClassifySendError(resp.StatusCode, fmt.Errorf("reading wecom upload error response: %w", err)) -======= respBody, readErr := io.ReadAll(resp.Body) if readErr != nil { - return "", fmt.Errorf( - "reading wecom upload error response: %w", readErr, + return "", channels.ClassifySendError( + resp.StatusCode, + fmt.Errorf("reading wecom upload error response: %w", readErr), ) ->>>>>>> 908fa8d (fix: resolve govet shadow and golines lint errors in wecom channels) } return "", channels.ClassifySendError( resp.StatusCode, @@ -385,15 +380,12 @@ func (c *WeComAppChannel) sendWeComMessage(ctx context.Context, accessToken stri defer resp.Body.Close() if resp.StatusCode != http.StatusOK { -<<<<<<< HEAD - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("reading wecom_app error response: %w", err)) -======= respBody, readErr := io.ReadAll(resp.Body) if readErr != nil { - return fmt.Errorf("reading wecom_app error response: %w", readErr) ->>>>>>> 908fa8d (fix: resolve govet shadow and golines lint errors in wecom channels) + return channels.ClassifySendError( + resp.StatusCode, + fmt.Errorf("reading wecom_app error response: %w", readErr), + ) } return channels.ClassifySendError( resp.StatusCode, diff --git a/pkg/channels/wecom/bot.go b/pkg/channels/wecom/bot.go index 8d64a91c6..96d5a961f 100644 --- a/pkg/channels/wecom/bot.go +++ b/pkg/channels/wecom/bot.go @@ -453,15 +453,12 @@ func (c *WeComBotChannel) sendWebhookReply(ctx context.Context, userID, content defer resp.Body.Close() if resp.StatusCode != http.StatusOK { -<<<<<<< HEAD - body, err := io.ReadAll(resp.Body) - if err != nil { - return channels.ClassifySendError(resp.StatusCode, fmt.Errorf("reading webhook error response: %w", err)) -======= body, readErr := io.ReadAll(resp.Body) if readErr != nil { - return fmt.Errorf("reading webhook error response: %w", readErr) ->>>>>>> 908fa8d (fix: resolve govet shadow and golines lint errors in wecom channels) + return channels.ClassifySendError( + resp.StatusCode, + fmt.Errorf("reading webhook error response: %w", readErr), + ) } return channels.ClassifySendError( resp.StatusCode,