{"record":{"id":"c69d2d10b057a1a5","repo":"jackwener/OpenCLI","slug":"xiaoyuzhou-credential-file-is-missing-access-token","errorCode":null,"errorMessage":"Xiaoyuzhou credential file is missing access_token or refresh_token: ${filePath}","messagePattern":"Xiaoyuzhou credential file is missing access_token or refresh_token: (.+?)","errorType":"exception","errorClass":"ConfigError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/auth.js","lineNumber":54,"sourceCode":"    if (!expiresAt && lastUpdatedTs > 0) {\n        expiresAt = lastUpdatedTs * 1000 + XIAOYUZHOU_TOKEN_TTL_MS;\n    }\n    return {\n        access_token: String(raw.access_token ?? raw.accessToken ?? '').trim(),\n        refresh_token: String(raw.refresh_token ?? raw.refreshToken ?? '').trim(),\n        expires_at: expiresAt,\n        device_id: String(raw.device_id ?? raw.deviceId ?? XIAOYUZHOU_DEFAULT_DEVICE_ID).trim() || XIAOYUZHOU_DEFAULT_DEVICE_ID,\n        device_properties: String(raw.device_properties ?? raw.deviceProperties ?? XIAOYUZHOU_DEFAULT_DEVICE_PROPERTIES),\n    };\n}\nexport function loadXiaoyuzhouCredentials() {\n    const filePath = getXiaoyuzhouCredentialFile();\n    if (fs.existsSync(filePath)) {\n        try {\n            const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));\n            const credentials = normalizeXiaoyuzhouCredentials(parsed);\n            if (!credentials.access_token || !credentials.refresh_token) {\n                throw new ConfigError(`Xiaoyuzhou credential file is missing access_token or refresh_token: ${filePath}`, 'Recreate the file with valid credentials.');\n            }\n            return credentials;\n        }\n        catch (error) {\n            if (error instanceof ConfigError) {\n                throw error;\n            }\n            throw new ConfigError(`Failed to parse Xiaoyuzhou credential file: ${filePath}`, `Ensure ${filePath} contains valid JSON. (${getErrorMessage(error)})`);\n        }\n    }\n    throw new ConfigError(`Missing Xiaoyuzhou credentials. Expected ${filePath}`, `Create ${filePath} with access_token and refresh_token.`);\n}\n\nexport function saveXiaoyuzhouCredentials(credentials) {\n    const filePath = getXiaoyuzhouCredentialFile();\n    fs.mkdirSync(path.dirname(filePath), { recursive: true });\n    fs.writeFileSync(filePath, `${JSON.stringify({\n        access_token: credentials.access_token,","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/auth.js#L36-L72","documentation":"loadXiaoyuzhouCredentials found the credential JSON file at filePath and parsed it, but after normalization either access_token or refresh_token was missing/empty, so it throws ConfigError with a remediation hint ('Recreate the file with valid credentials.'). The library requires both tokens to refresh or authorize xiaoyuzhou (小宇宙) API requests.","triggerScenarios":"The JSON file exists but was hand-edited and a token field was deleted or left empty; normalizeXiaoyuzhouCredentials dropped the field because the key name didn't match; the file holds only one of the two tokens; fields are present but null/whitespace.","commonSituations":"Partial copy-paste of credentials during setup; exporting a token set that only contained access_token; a schema/key rename (e.g. refreshToken vs refresh_token) so normalization drops the value; truncated file write.","solutions":["Open filePath and confirm both access_token and refresh_token keys exist with non-empty string values","Re-create the file with a complete credential export from the xiaoyuzhou source (both tokens)","Check key naming/casing matches what normalizeXiaoyuzhouCredentials expects (refresh_token vs refreshToken)","Re-run the auth/login flow to regenerate fresh tokens and save them via saveXiaoyuzhouCredentials"],"exampleFix":"// before (credentials.json)\n{ \"access_token\": \"abc123\" }\n// after\n{ \"access_token\": \"abc123\", \"refresh_token\": \"def456\" }","handlingStrategy":"validation","validationCode":"const fs = require('fs');\nfunction validateXiaoyuzhouCreds(filePath) {\n  if (!fs.existsSync(filePath)) throw new Error(`missing ${filePath}`);\n  const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));\n  if (!parsed.access_token || !parsed.refresh_token) {\n    throw new Error(`${filePath} must contain non-empty access_token and refresh_token`);\n  }\n}\nvalidateXiaoyuzhouCreds('./config/xiaoyuzhou.credentials.json');","typeGuard":"function hasBothTokens(c) {\n  return typeof c?.access_token === 'string' && c.access_token.length > 0\n      && typeof c?.refresh_token === 'string' && c.refresh_token.length > 0;\n}","tryCatchPattern":"try {\n  const creds = loadXiaoyuzhouCredentials();\n} catch (err) {\n  if (/missing access_token or refresh_token/.test(err.message)) {\n    await runXiaoyuzhouAuthFlow(); // regenerate and save both tokens\n  } else throw err;\n}","preventionTips":["Never hand-edit credential files; generate them via the auth flow","Validate both token fields in CI before jobs that use xiaoyuzhou","Keep key names exactly access_token/refresh_token as the normalizer expects"],"tags":["config","credentials","token","xiaoyuzhou"],"backgroundTag":"missing-credential-field","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}