CherryHQ/cherry-studio · error · Error
Invalid credentials format in ${tokenPath}
Error message
Invalid credentials format in ${tokenPath} What it means
Thrown by loadCredentials() in WeChatProtocol when the credentials file at tokenPath exists and parses as JSON but fails Zod validation against CredentialsSchema ({token:string, baseUrl:string, accountId:string, userId:string}). This is a persisted-state corruption: the file is present but its shape does not match what the bot expects. ENOENT (file missing) is handled separately and returns undefined — only a present-but-malformed file throws.
Source
Thrown at src/main/ai/channels/adapters/wechat/WeChatProtocol.ts:561
return {
from_user_id: '',
to_user_id: userId,
client_id: randomUUID(),
message_type: MessageType.BOT,
message_state: MessageState.FINISH,
context_token: contextToken,
item_list: [{ type: MessageItemType.TEXT, text_item: { text } }]
}
}
// --------------- Auth ---------------
async function loadCredentials(tokenPath: string): Promise<Credentials | undefined> {
try {
const raw = await readFile(tokenPath, 'utf8')
const result = CredentialsSchema.safeParse(JSON.parse(raw))
if (!result.success) {
throw new Error(`Invalid credentials format in ${tokenPath}`)
}
return result.data
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
return undefined
}
throw error
}
}
async function saveCredentials(credentials: Credentials, tokenPath: string): Promise<void> {
await mkdir(path.dirname(tokenPath), { recursive: true, mode: 0o700 })
await writeFile(tokenPath, `${JSON.stringify(credentials, null, 2)}\n`, { mode: 0o600 })
await chmod(tokenPath, 0o600)
}
async function clearCredentials(tokenPath: string): Promise<void> {
await rm(tokenPath, { force: true })View on GitHub (pinned to 726446b54c)
Solutions
- Delete the credentials file at tokenPath (it is at feature.agents.channels/weixin_bot_<channelId>.json per WeChatAdapter.ts:22) to force a fresh QR login.
- Validate the file contents manually — it must have all four string fields: token, baseUrl, accountId, userId.
- If this is a migration issue, write a one-time migration that backfills missing fields or removes the old file.
- Run WeixinBot.login({force:true}) to regenerate the credentials file from a new QR scan.
Example fix
// before — corrupt file blocks startup; user must find and delete it manually
const existing = await loadCredentials(options.tokenPath) // throws
// after — caller treats invalid credentials as 'no credentials' and re-logins
let existing: Credentials | undefined
try {
existing = await loadCredentials(options.tokenPath)
} catch (e) {
logger.warn('Credentials file corrupt, forcing re-login', { error: (e as Error).message })
await rm(options.tokenPath, { force: true })
}
if (existing) return existing
// proceed to QR login Defensive patterns
Strategy: validation
Validate before calling
// Validate the credentials file shape BEFORE handing it to loadCredentials,
// or treat loadCredentials failure as 'no credentials' and force re-login.
const CREDENTIALS_SHAPE = {
token: 'string',
baseUrl: 'string',
accountId: 'string',
userId: 'string'
} as const
function looksLikeCredentials(raw: unknown): boolean {
if (typeof raw !== 'object' || raw === null) return false
return Object.entries(CREDENTIALS_SHAPE).every(
([k, t]) => typeof (raw as Record<string, unknown>)[k] === t
)
}
// Safer wrapper: corrupt file → force re-login instead of propagating the throw
async function loadCredentialsOrForceRelogin(tokenPath: string): Promise<Credentials | undefined> {
try {
return await loadCredentials(tokenPath)
} catch (e) {
logger.warn('Credentials file invalid, removing to force re-login', { error: (e as Error).message })
await rm(tokenPath, { force: true })
return undefined
}
} Type guard
// Reuse the Zod schema already defined in WeChatProtocol.ts:139
// CredentialsSchema = z.object({ token: z.string(), baseUrl: z.string(), accountId: z.string(), userId: z.string() })
function isValidCredentials(raw: unknown): raw is Credentials {
return CredentialsSchema.safeParse(raw).success
} Try / catch
// In the login path, treat an invalid credentials file as 'no credentials'
// rather than letting the throw propagate to the user as a hard error.
let existing: Credentials | undefined
try {
existing = await loadCredentials(options.tokenPath)
} catch {
existing = undefined // force QR login
}
if (existing) return existing Prevention
- Never hand-edit the weixin_bot_<channelId>.json file — it is written by saveCredentials with a fixed schema.
- If migrating schema, add a migration step that backfills new fields or removes the old file before the new code reads it.
- Run WeixinBot.login({force:true}) to regenerate a valid credentials file after schema changes.
- Write a backup of credentials before schema-migrating, and validate with CredentialsSchema.safeParse before trusting.
When it happens
Trigger: readFile succeeds, JSON.parse succeeds (otherwise it would throw SyntaxError, not this message — but note the catch only re-throws non-ENOENT errors, so a SyntaxError from JSON.parse would propagate, not this message), then CredentialsSchema.safeParse fails. This means the JSON is valid but missing required fields or has wrong types: e.g. {token:'x'} without baseUrl/accountId/userId, or fields with null where strings are required.
Common situations: An older app version wrote a credentials file with a different schema (e.g. missing accountId added in a later migration); manual editing of the token file corrupted it; a partial write left a truncated JSON object (though that usually fails JSON.parse first); the file was overwritten by another process writing a different JSON structure.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Agent storage path escapes its root: ${target}
- Agent storage root must be a real directory: ${root}
- Agent storage path parent is not a directory: ${current}
- Invalid agent id for data directory: ${agentId}
- QQ Bot AppID and ClientSecret are required
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/e3d2afadd327097d.
Report an issue: GitHub.