{"record":{"id":"ec0a13bbef11a81e","repo":"jackwener/OpenCLI","slug":"auth-required-ec0a13","errorCode":"AUTH_REQUIRED","errorMessage":"Xiaoyuzhou refresh token is missing","messagePattern":"Xiaoyuzhou refresh token is missing","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/auth.js","lineNumber":129,"sourceCode":"        'x-jike-device-id': credentials.device_id || XIAOYUZHOU_DEFAULT_DEVICE_ID,\n        'x-jike-device-properties': credentials.device_properties ?? XIAOYUZHOU_DEFAULT_DEVICE_PROPERTIES,\n    };\n    if (credentials.access_token) {\n        headers['x-jike-access-token'] = credentials.access_token;\n    }\n    if (includeRefreshToken && credentials.refresh_token) {\n        headers['x-jike-refresh-token'] = credentials.refresh_token;\n    }\n    if (includeLocalTime) {\n        headers['Local-Time'] = new Date().toISOString();\n        headers.Timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';\n    }\n    return headers;\n}\n\nexport async function refreshXiaoyuzhouCredentials(credentials, fetchImpl = fetch) {\n    if (!credentials.refresh_token) {\n        throw createXiaoyuzhouAuthError('Xiaoyuzhou refresh token is missing');\n    }\n    let response;\n    try {\n        response = await fetchImpl(`${XIAOYUZHOU_API_BASE_URL}/app_auth_tokens.refresh`, {\n            method: 'POST',\n            headers: buildXiaoyuzhouHeaders(credentials, {\n                contentType: 'application/x-www-form-urlencoded; charset=utf-8',\n                includeLocalTime: true,\n                includeRefreshToken: true,\n            }),\n            signal: AbortSignal.timeout(20_000),\n        });\n    }\n    catch (error) {\n        throw new CommandExecutionError(`Failed to refresh Xiaoyuzhou credentials: ${getErrorMessage(error)}`);\n    }\n    const bodyText = await response.text();\n    if (!response.ok) {","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/auth.js#L111-L147","documentation":"createXiaoyuzhouAuthError thrown from refreshXiaoyuzhouCredentials when the stored credentials have no refresh_token. Without it the library cannot call the /app_auth_tokens.refresh endpoint to obtain a new access token, so any authenticated request fails with AUTH_REQUIRED.","triggerScenarios":"requestXiaoyuzhouJson detects an expired access token and calls refreshed → refreshXiaoyuzhouCredentials, but credentials.refresh_token is empty/undefined (never captured at login, or wiped by a prior bad refresh).","commonSituations":"Login flow stored only the access token; a previous refresh wrote empty tokens (normalizeXiaoyuzhouCredentials persisted '' for refresh_token); config/env var holding credentials was truncated or never set.","solutions":["Re-run the Xiaoyuzhou login/auth flow to obtain a fresh access_token + refresh_token pair and persist both","Inspect stored credentials and ensure refresh_token is present and non-empty","Guard the refresh path: if refresh_token is missing, fail fast into re-login instead of calling the refresh endpoint","Check that the field name is correct (refresh_token) wherever credentials are serialized"],"exampleFix":"// before: refreshing with empty token\nawait refreshXiaoyuzhouCredentials({ access_token: 'x', refresh_token: '' });\n// after: re-login first, then refresh\nif (!credentials.refresh_token) {\n  credentials = await xiaoyuzhouLogin(); // persists both tokens\n}\nawait refreshXiaoyuzhouCredentials(credentials);","handlingStrategy":"validation","validationCode":"if (!credentials || typeof credentials.refresh_token !== 'string' || !credentials.refresh_token) {\n  throw new Error('Xiaoyuzhou refresh token missing — re-run login before making API calls');\n}","typeGuard":"function hasRefreshToken(c) { return typeof c === 'object' && c !== null && typeof c.refresh_token === 'string' && c.refresh_token.length > 0; }","tryCatchPattern":"try {\n  const data = await requestXiaoyuzhouJson(url, credentials);\n} catch (e) {\n  if (e.code === 'AUTH_REQUIRED' && /refresh token is missing/.test(e.message)) {\n    credentials = await xiaoyuzhouLogin(); // obtain a fresh token pair\n    return await requestXiaoyuzhouJson(url, credentials);\n  }\n  throw e;\n}","preventionTips":["Persist both access_token and refresh_token at login time","Never write empty strings over an existing refresh_token","Validate credential shape at load time","Centralize token storage so partial updates can't drop fields"],"tags":["auth","token","xiaoyuzhou"],"backgroundTag":"missing-refresh-token","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}