chenhg5/cc-connect · error

%s: status %d: %s

Error message

%s: status %d: %s

What it means

This is the standard error returned for any non-2xx response from the Google Chat REST API. httpErrorBody reads up to 2048 bytes of the API's error body, trims it, and combines it with the HTTP status code and the caller's prefix (e.g. "googlechat: send message"), giving the developer both the status and Google's error explanation.

Source

Thrown at platform/googlechat/googlechat.go:358

	if idx := strings.Index(rest, threadSep); idx != -1 {
		return replyContext{space: rest[:idx], thread: rest[idx+len(threadSep):]}, nil
	}
	// User-scoped keys append ":<user>" where user is "users/<id>"; strip a
	// trailing "users/..." segment to recover the bare space.
	if idx := strings.LastIndex(rest, ":users/"); idx != -1 {
		return replyContext{space: rest[:idx]}, nil
	}
	return replyContext{space: rest}, nil
}

// httpErrorBody reads up to 2048 bytes from resp.Body, closes it, and returns
// an error combining prefix, status code, and the response snippet.
func httpErrorBody(resp *http.Response, prefix string) error {
	b, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
	if err := resp.Body.Close(); err != nil {
		return fmt.Errorf("%s: status %d: %s (close body: %v)", prefix, resp.StatusCode, strings.TrimSpace(string(b)), err)
	}
	return fmt.Errorf("%s: status %d: %s", prefix, resp.StatusCode, strings.TrimSpace(string(b)))
}

// coalesce returns s if non-empty, otherwise def.
func coalesce(s, def string) string {
	if s != "" {
		return s
	}
	return def
}

// messageURL returns the Chat messages endpoint for rc's space, appending the
// messageReplyOption query when a thread is known.
func messageURL(rc replyContext) string {
	u := chatAPIBase + rc.space + "/messages"
	if rc.thread != "" {
		u += "?messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD"
	}
	return u

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Read the status and embedded Google error body in the message — it names the exact cause (e.g. PERMISSION_DENIED, NOT_FOUND)
  2. For 403/404, verify the bot is still a member of the target space and the space name (spaces/AAAA) is valid; re-create the session if the space changed
  3. For 401, refresh the OAuth token / re-authorize scopes (chat.messages); for 429, add backoff/retry around sends

Example fix

// before
POST https://chat.googleapis.com/v1/spaces/OLD/messages -> 404 NOT_FOUND
// after
Fetch the correct current space via spaces.list (or re-add the bot) and use the new space name in the reply context
Defensive patterns

Strategy: retry

Validate before calling

// before sending, confirm the bot can access the space
resp, _ := client.Get("https://chat.googleapis.com/v1/" + spaceName + "?access_token=" + tok)
// treat non-200 as "space unreachable" and skip/recreate session

Try / catch

err := p.Send(ctx, rc, msg)
if err != nil {
    var ge *googleapi.Error
    if errors.As(err, &ge) {
        switch ge.Code {
        case 429: time.Sleep(backoff); retry()
        case 401: refreshToken(); retry()
        case 403, 404: invalidateSession(rc)
        }
    }
}

Prevention

When it happens

Trigger: Any Chat API call via doRequest that returns 4xx/5xx: posting to a deleted or inaccessible space (404), bot not added to the space (403), invalid thread key (400), quota exceeded (429), or Google-side 5xx.

Common situations: Bot removed from the space; wrong space/thread resource name stored in a session; service account lacking chat.messages authorization scope; exceeding API rate limits; expired OAuth token surfacing as 401.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/eafa7a0062dac96c. Report an issue: GitHub.