charmbracelet/crush · error
failed to decode cancel question batch response: %w
Error message
failed to decode cancel question batch response: %w
What it means
After a 200 from /workspaces/{id}/questions/cancel, CancelQuestionBatch decodes proto.QuestionAnswerResponse to learn whether a question was cancelled (Resolved). A decode failure — empty body, non-JSON proxy page, schema mismatch — raises this error, leaving the cancel outcome ambiguous.
Source
Thrown at internal/client/proto.go:729
}
return resp.Resolved, nil
}
// CancelQuestionBatch cancels the pending question batch on a
// workspace. Returns true if a question was cancelled, false if
// none was pending.
func (c *Client) CancelQuestionBatch(ctx context.Context, id string) (bool, error) {
rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/questions/cancel", id), nil, nil, http.Header{})
if err != nil {
return false, fmt.Errorf("failed to cancel question batch: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
return false, fmt.Errorf("failed to cancel question batch: status code %d", rsp.StatusCode)
}
var resp proto.QuestionAnswerResponse
if err := json.NewDecoder(rsp.Body).Decode(&resp); err != nil {
return false, fmt.Errorf("failed to decode cancel question batch response: %w", err)
}
return resp.Resolved, nil
}
// SetPermissionsSkipRequests sets the skip-requests flag for a workspace.
func (c *Client) SetPermissionsSkipRequests(ctx context.Context, id string, skip bool) error {
rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/permissions/skip", id), nil, jsonBody(proto.PermissionSkipRequest{Skip: skip}), http.Header{"Content-Type": []string{"application/json"}})
if err != nil {
return fmt.Errorf("failed to set permissions skip requests: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to set permissions skip requests: status code %d", rsp.StatusCode)
}
return nil
}
// GetPermissionsSkipRequests retrieves the skip-requests flag for a workspace.View on GitHub (pinned to 7944b8e522)
Solutions
- Inspect the raw 200 response body with curl or a logging proxy.
- Align client and server versions so proto.QuestionAnswerResponse matches.
- Check reverse proxy/gateway behavior that could rewrite responses.
- Retry the cancel; note a false Resolved just means nothing was pending.
Defensive patterns
Strategy: retry
Type guard
func isCancelDecodeError(err error) bool {
return err != nil && strings.Contains(err.Error(), "failed to decode cancel question batch response")
} Try / catch
ok, err := client.CancelQuestionBatch(ctx, wsID)
if isCancelDecodeError(err) {
// 200 but unreadable body; retry once — cancel is idempotent
return retryCancel(ctx, wsID, 1)
} Prevention
- Keep client/server versions aligned
- Log raw 200 bodies when decode fails to identify proxy tampering
- Retry cancels — Resolved makes them idempotent
- Test cancel flows through your full proxy chain, not just direct
When it happens
Trigger: Server returns 200 with an empty or non-JSON body; an intermediary substitutes an HTML page; server response shape changed relative to proto.QuestionAnswerResponse.
Common situations: Proxy/gateway HTML interstitial; truncated response on unstable connections; client/server version skew after an upgrade.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to decode answer question batch response: %w
- failed to decode session agent info: %w
- failed to decode grant permission response: %w
- failed to decode response: %w
- failed to decode project init response: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/5eadff9595e5879b.
Report an issue: GitHub.