{"record":{"id":"97d260978360e5b2","repo":"Tencent/WeKnora","slug":"yuque-connection-failed-w","errorCode":null,"errorMessage":"yuque connection failed: %w","messagePattern":"yuque connection failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/datasource/connector/yuque/connector.go","lineNumber":38,"sourceCode":"\n// Connector implements datasource.Connector for Yuque.\ntype Connector struct{}\n\n// NewConnector creates a new Yuque connector.\nfunc NewConnector() *Connector { return &Connector{} }\n\n// Type returns the connector type identifier.\nfunc (c *Connector) Type() string { return types.ConnectorTypeYuque }\n\n// Validate verifies the given credentials by pinging the current-user endpoint.\nfunc (c *Connector) Validate(ctx context.Context, config *types.DataSourceConfig) error {\n\tcfg, err := parseYuqueConfig(config)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcli := newClient(cfg)\n\tif err := cli.Ping(ctx); err != nil {\n\t\treturn fmt.Errorf(\"yuque connection failed: %w\", err)\n\t}\n\treturn nil\n}\n\n// ResolveResourceAncestors has nothing to do for Yuque: repositories are a flat\n// list with no nesting, so a selection has no ancestors to reveal.\nfunc (c *Connector) ResolveResourceAncestors(\n\tctx context.Context, config *types.DataSourceConfig, resourceIDs []string,\n) ([]string, error) {\n\treturn []string{}, nil\n}\n\n// ListResources returns all repos (personal + team) accessible to the token.\n// Serial fetch for v1 (user groups typically <10). TODO(perf): parallelize if slow.\nfunc (c *Connector) ListResources(\n\tctx context.Context, config *types.DataSourceConfig, parentID string,\n) ([]types.Resource, error) {\n\t// Yuque resources are a flat list of repositories (no nesting), so a","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/datasource/connector/yuque/connector.go#L20-L56","documentation":"Connector.Validate pings GET /api/v2/user to verify credentials; any error from cli.Ping (transport failure, 401/403 ErrInvalidCredentials, 429 exhausted retries, 4xx/5xx API error, decode failure) is wrapped as \"yuque connection failed: %w\". This is the error surfaced when a user saves/tests a Yuque datasource configuration, so it aggregates every possible failure mode behind one wrapper. Use errors.Is/As on the wrapped chain to distinguish causes.","triggerScenarios":"Validate(ctx, config) is called with credentials/baseURL whose /api/v2/user call fails: revoked or mistyped API token (401/403), unreachable host or timeout, exhausted rate limit (429 after 3 retries), 5xx after retry, non-2xx API error, or a 200 body that fails to decode.","commonSituations":"User pastes a token with whitespace/newline from a copy-paste; token was regenerated or expired; wrong baseURL for a private Yuque deployment; network egress blocked from the WeKnora deployment to yuque.com; token belongs to a deactivated account.","solutions":["Unwrap with errors.Is(err, datasource.ErrInvalidCredentials) — if it matches, the token is bad: regenerate it in Yuque settings (Settings → Token) and update the datasource config.","Test the token manually: curl -H 'X-Auth-Token: <token>' https://www.yuque.com/api/v2/user and check the response.","Verify the configured base URL is reachable from the WeKnora deployment (DNS, proxy, firewall); note docker-compose containers may need the host network or proxy env.","If the error says rate limited, wait ~5 minutes and retry validation.","Ensure the config field mapping is right (api_token / base_url keys) and the token string is trimmed of whitespace."],"exampleFix":"err := connector.Validate(ctx, cfg)\nif err != nil {\n    if errors.Is(err, datasource.ErrInvalidCredentials) {\n        // bad token → prompt user to re-enter the API token\n    } else if errors.Is(err, context.DeadlineExceeded) {\n        // network/timeout → check connectivity to the Yuque host\n    }\n}","handlingStrategy":"try-catch","validationCode":"// Go: check token shape before calling Validate\ntoken := strings.TrimSpace(cfg.APIToken)\nif token == \"\" || len(token) < 16 {\n    return errors.New(\"yuque api token missing or malformed — generate one in Yuque settings\")\n}\nif !strings.HasPrefix(cfg.BaseURL, \"https://\") {\n    return errors.New(\"yuque base url must be https\")\n}","typeGuard":"// Go: classify the wrapped validation failure\nfunc classifyValidateError(err error) string {\n    switch {\n    case errors.Is(err, datasource.ErrInvalidCredentials):\n        return \"invalid-credentials\"\n    case errors.Is(err, context.DeadlineExceeded), errors.Is(err, context.Canceled):\n        return \"timeout\"\n    case strings.Contains(err.Error(), \"rate limited\"):\n        return \"rate-limited\"\n    case strings.Contains(err.Error(), \"decode response\"):\n        return \"schema-mismatch\"\n    default:\n        return \"network-or-server\"\n    }\n}","tryCatchPattern":"if err := connector.Validate(ctx, dsConfig); err != nil {\n    switch classifyValidateError(err) {\n    case \"invalid-credentials\":\n        ui.ShowReconnectPrompt(\"yuque\")\n    case \"rate-limited\":\n        time.Sleep(5 * time.Minute)\n        retry()\n    default:\n        ui.ShowError(fmt.Sprintf(\"yuque connection failed: %v\", err))\n    }\n    return\n}","preventionTips":["Trim whitespace/newlines from pasted tokens before saving the config.","Run Validate on save and schedule periodic re-validation to catch revoked tokens early.","Document the correct base URL per deployment (yuque.com vs self-hosted) in the setup UI.","Verify outbound HTTPS to the Yuque host from inside the deployment container (e.g. wget the /api/v2/user endpoint).","Regenerate tokens after staff/permission changes instead of sharing long-lived personal tokens."],"tags":["connection-validation","yuque","credentials","config"],"backgroundTag":"connection-validation-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}