multica-ai/multica · error
vcs: token unauthorized
Error message
vcs: token unauthorized
What it means
VCS integration sentinel error: ValidateToken asked the Forgejo/Gitea/GitLab instance about a token and the instance answered HTTP 401/403 — the token is not accepted. Callers surface it as a connect-time validation failure, distinct from transport/instance errors.
Source
Thrown at server/internal/integrations/vcs/vcs.go:42
const (
KindForgejo Kind = "forgejo"
KindGitea Kind = "gitea"
KindGitLab Kind = "gitlab"
)
// Valid reports whether k is a known provider kind.
func (k Kind) Valid() bool {
switch k {
case KindForgejo, KindGitea, KindGitLab:
return true
}
return false
}
// ErrUnauthorized is returned by ValidateToken when the instance rejects the
// token (HTTP 401/403). Callers surface it as a connect-time validation
// failure distinct from transport/instance errors.
var ErrUnauthorized = errors.New("vcs: token unauthorized")
// EventKind is the normalized webhook event category. Anything a provider does
// not model maps to EventOther and is acknowledged but ignored.
type EventKind int
const (
EventOther EventKind = iota
EventPullRequest
EventCIStatus
)
// PullRequestEvent is the provider-agnostic shape of a pull/merge request
// webhook. State is already normalized to one of open/closed/merged/draft, so
// the handler never re-derives it. GitLab "merge requests" map onto the same
// struct.
type PullRequestEvent struct {
// Action is the raw provider action (e.g. "opened", "closed", "merge").
// The handler only needs to know whether it is terminal; see Terminal.View on GitHub (pinned to 2c0912b6ec)
Solutions
- Generate a fresh PAT in the provider UI with the required scopes and re-enter it.
- Verify the token manually: curl -H "Authorization: Bearer <token>" https://instance/api/v1/user (or /api/v4/user for GitLab) — a 401/403 confirms rejection.
- Check token expiration policy (GitLab instance-level expiry limits) and IP allowlists on the instance.
Example fix
// before
if err := vcs.ValidateToken(ctx, kind, baseURL, token); err != nil {
log.Printf("vcs connect failed: %v", err) // conflates auth with network
}
// after
err := vcs.ValidateToken(ctx, kind, baseURL, token)
switch {
case errors.Is(err, vcs.ErrUnauthorized):
respond(w, 400, "token rejected by the instance — check value, scopes, and expiry")
case err != nil:
respond(w, 502, "could not reach the instance to validate the token")
} Defensive patterns
Strategy: try-catch
Validate before calling
// cheap pre-flight: does the token fetch the authenticated user?
req, _ := http.NewRequest("GET", instanceURL+"/api/v1/user", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err == nil && (resp.StatusCode == 401 || resp.StatusCode == 403) {
return errors.New("token rejected — fix before connecting")
} Try / catch
err := vcs.ValidateToken(ctx, kind, baseURL, token)
if err != nil {
if errors.Is(err, vcs.ErrUnauthorized) {
// credential problem: ask for a new token, do not retry
return respondTokenRejected(w)
}
// everything else is transport/instance — safe to retry with backoff
return retryableError(err)
} Prevention
- Store token creation/expiry metadata and warn before provider-side expiry.
- Distinguish 401/403 (this error) from network errors in dashboards — only the latter merits retries.
- Scope PATs minimally and re-validate at connect time rather than trusting stored values.
When it happens
Trigger: Calling vcs.ValidateToken with a revoked, expired, mistyped, or insufficient-scope PAT against a Forgejo, Gitea, or GitLab instance. Only 401/403 responses map to this error; network failures and 5xx do not.
Common situations: Personal access token rotated or expired since it was saved; token pasted with whitespace or truncated; token lacks the scopes needed by the endpoint being probed (read:user, api, etc.); SSO/session enforcement on GitLab rejecting plain PATs.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Invalid desktop runtime config JSON: ${err instanceof Error
- agent execution context requires MULTICA_TOKEN to be a task-
- %s is not available inside a daemon-managed task%s
- %s is not available inside a daemon-managed task
- list workspaces: %w
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/a4fe254f2327dde6.
Report an issue: GitHub.