chenhg5/cc-connect · critical

empty accessToken in response

Error message

empty accessToken in response

What it means

The DingTalk token endpoint returned HTTP 200 with decodable JSON, but the accessToken field was empty. The API succeeded at the transport level yet did not issue a token, so no authenticated API calls can proceed. (The surrounding code falls back to the documented 7200s expiry when expireIn is missing, but an empty token is always fatal.)

Source

Thrown at platform/dingtalk/dingtalk.go:773

		return "", fmt.Errorf("do request: %w", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return "", fmt.Errorf("api returned status %d: %s", resp.StatusCode, body)
	}

	var tokenResp struct {
		AccessToken string `json:"accessToken"`
		ExpireIn    int    `json:"expireIn"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
		return "", fmt.Errorf("decode response: %w", err)
	}

	if tokenResp.AccessToken == "" {
		return "", fmt.Errorf("empty accessToken in response")
	}

	// Cache token with 5 minutes buffer before expiry.
	// When the server omits expireIn (or sends 0/negative), fall back to the
	// documented DingTalk default (7200s = 2h) — without this, tokenExpiry
	// would land at time.Now() and every subsequent getAccessToken() would
	// re-fetch a fresh token, hammering the access-token API.
	p.accessToken = tokenResp.AccessToken
	expiry := tokenResp.ExpireIn
	if expiry <= 0 {
		slog.Warn("dingtalk: missing/invalid expireIn in token response, defaulting to 7200s", "got", tokenResp.ExpireIn)
		expiry = 7200
	}
	if expiry > 300 {
		expiry -= 300 // 5 minute buffer
	}
	p.tokenExpiry = time.Now().Add(time.Duration(expiry) * time.Second)

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Log the full response JSON to see companion error fields DingTalk may include.
  2. In the DingTalk developer console, confirm the app is published, enabled, and has the required API permissions (robot, media, message).
  3. Verify you're using the correct app type (enterprise internal app) and its appKey/appSecret.
  4. Refresh the access token once after fixing permissions, then restart cc-connect.
  5. Check DingTalk changelog/announcements for recent API behavior changes.
Defensive patterns

Strategy: try-catch

Validate before calling

// startup credential sanity check
if p.clientID == "" || p.clientSecret == "" {
    return errors.New("dingtalk: missing appKey/appSecret in config")
}

Try / catch

tok, err := p.getAccessToken()
if err != nil {
    if strings.Contains(err.Error(), "empty accessToken") {
        // do not retry blindly: fix app permissions/publish state first
        return fmt.Errorf("dingtalk did not issue a token — check app console: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Any token-requiring call when DingTalk returns 200 with a JSON body lacking a non-empty accessToken — usually an app-credentials or app-status problem surfaced as a success envelope, or a credential pair that authenticates but has no robot/API scope granted.

Common situations: App created but robot/API permissions not granted in the DingTalk console, app pending review/publish, using a mini-app appKey where a corp app is required, or DingTalk rolling out schema changes.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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