{"record":{"id":"a7865bf28704fc12","repo":"jlcodes99/cockpit-tools","slug":"token-a7865b","errorCode":null,"errorMessage":"Token 刷新失败: {}","messagePattern":"Token 刷新失败: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cockpit-core/src/modules/import.rs","lineNumber":690,"sourceCode":"                                modules::account::update_account_notes(&new_account.id, notes)\n                            {\n                                new_account = acc;\n                            }\n                        }\n                        modules::logger::log_info(&format!(\"导入账号成功: {}\", new_account.email));\n                        imported.push(new_account);\n                    }\n                    Err(e) => {\n                        let msg = format!(\"保存失败: {}\", e);\n                        modules::logger::log_error(&format!(\"保存账号失败 {}: {}\", email, msg));\n                        failed.push(FileImportFailure { email, error: msg });\n                    }\n                }\n            }\n            Err(e) => {\n                let label = entry.email.as_deref().unwrap_or(\"unknown\").to_string();\n                let msg = format!(\"Token 刷新失败: {}\", e);\n                modules::logger::log_error(&format!(\"{}: {}\", label, msg));\n                failed.push(FileImportFailure {\n                    email: label,\n                    error: msg,\n                });\n            }\n        }\n    }\n\n    modules::logger::log_info(&format!(\n        \"文件导入完成，成功 {} 个，失败 {} 个\",\n        imported.len(),\n        failed.len()\n    ));\n\n    if !imported.is_empty() {\n        modules::websocket::broadcast_data_changed(\"import_from_files\");\n    }\n","sourceCodeStart":672,"sourceCodeEnd":708,"githubUrl":"https://github.com/jlcodes99/cockpit-tools/blob/1ed8b77992d62ca81fabf744deb0839ad361d5bf/crates/cockpit-core/src/modules/import.rs#L672-L708","documentation":"Emitted in import_from_files_logic when modules::oauth::refresh_access_token fails while validating a candidate account found in an imported file. The refresh error `e` is wrapped as \"Token 刷新失败: {}\" and the entry is added to the failed list (labeled with the entry email or 'unknown'); import of remaining entries continues.","triggerScenarios":"File import where a candidate refresh_token is invalid, expired, or revoked so the OAuth token endpoint rejects it; network failure during the token request; malformed refresh_token extracted from the file.","commonSituations":"Importing old account dumps whose refresh tokens were rotated or revoked; hand-edited or truncated JSON files; tokens from a different environment/client_id; offline machine or blocked network during import.","solutions":["Check the wrapped cause: if it is an HTTP 400/invalid_grant, the token is dead — the account must be re-logged-in rather than imported.","Verify the source file's refresh_token values are current and unrotated; export a fresh dump from the working tool.","Confirm network access to the OAuth token endpoint and retry the import.","Inspect result.failed after import; re-export/re-import only the failed emails after re-authenticating them.","Ensure the file's accounts belong to the same provider/client this build expects."],"exampleFix":"// before: assuming all entries import\nlet result = import_from_files_logic(&paths).await?;\n// after: handle stale refresh tokens explicitly\nlet result = import_from_files_logic(&paths).await?;\nfor f in &result.failed {\n    if f.error.contains(\"Token 刷新失败\") {\n        eprintln!(\"{}: refresh token rejected, needs re-login\", f.email);\n    }\n}","handlingStrategy":"validation","validationCode":"// Validate candidate refresh tokens before attempting refresh\nfn candidate_token_valid(entry: &ImportEntry) -> bool {\n    let t = entry.refresh_token.trim();\n    !t.is_empty() && t.len() >= 20 && t.chars().all(|c| !c.is_control())\n}","typeGuard":"fn is_refresh_token_error(failure: &FileImportFailure) -> bool {\n    failure.error.starts_with(\"Token 刷新失败: \")\n}","tryCatchPattern":"let result = import_from_files_logic(&paths).await?;\nlet stale: Vec<_> = result.failed.iter().filter(|f| is_refresh_token_error(f)).collect();\nfor f in stale {\n    eprintln!(\"{}: refresh token rejected — re-authenticate this account\", f.email);\n}","preventionTips":["Export account files from a live, recently-authenticated tool — old dumps often hold rotated tokens.","Validate JSON structure and token length before importing.","Keep source files untouched (no manual edits/truncation) between export and import.","Ensure network access to the OAuth endpoint during import.","Review the failed list in FileImportResult after each import."],"tags":["oauth","token-refresh","import","network"],"backgroundTag":"oauth-refresh-token-rejected","analyzedSha":"1ed8b77992d62ca81fabf744deb0839ad361d5bf","analyzedAt":"2026-09-05T09:51:41.178Z","contentChangedAt":"2026-09-05T09:51:41.178Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}