{"record":{"id":"63840addeeb59f9d","repo":"jlcodes99/cockpit-tools","slug":"codex-63840a","errorCode":null,"errorMessage":"Codex 导入失败 {}: {}","messagePattern":"Codex 导入失败 (.+?): (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/modules/codex_account_mutations_quota.rs","lineNumber":142,"sourceCode":"                }\n                logger::log_info(&format!(\"Codex 导入成功: {}\", account.email));\n                imported.push(account);\n            }\n            Err(e) => {\n                if is_disk_full_error_message(&e) {\n                    logger::log_error(&format!(\n                        \"Codex 导入因磁盘空间不足终止: label={}, imported={}, error={}\",\n                        label,\n                        imported.len(),\n                        e\n                    ));\n                    return Err(format!(\n                        \"磁盘空间不足，已终止导入（已成功 {} 个）。{}\",\n                        imported.len(),\n                        e\n                    ));\n                }\n                logger::log_error(&format!(\"Codex 导入失败 {}: {}\", label, e));\n                failed.push(CodexFileImportFailure {\n                    email: label,\n                    error: e,\n                });\n            }\n        }\n    }\n\n    for (content, label, auth_file_plan_type) in fallback_files {\n        progress_index += 1;\n        if let Some(app_handle) = crate::get_app_handle() {\n            use tauri::Emitter;\n            let _ = app_handle.emit(\n                \"codex:file-import-progress\",\n                serde_json::json!({\n                    \"current\": progress_index,\n                    \"total\": total,\n                    \"email\": &label,","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/jlcodes99/cockpit-tools/blob/1ed8b77992d62ca81fabf744deb0839ad361d5bf/src-tauri/src/modules/codex_account_mutations_quota.rs#L124-L160","documentation":"Per-file Codex account import failure log emitted inside import_from_files (src-tauri/src/modules/codex_account_mutations_quota.rs:142). When upsert_account_with_hints fails for one credential file and the error is NOT a disk-full condition, the label (file name/email) and underlying error are logged and the file is appended to the `failed` list; the import of remaining files continues. It is a non-fatal, per-account failure, not an abort of the whole batch.","triggerScenarios":"Calling import_from_files with old-format candidate files where upsert_account_with_hints(tokens, account_id_hint, None) returns Err — e.g. a token file missing accessToken/access_token/id_token/refresh_token fields, malformed JSON, an account_id_hint that conflicts with stored data, or a storage write error that is not disk-full.","commonSituations":"Bulk-importing a directory of auth JSON files where some are stale, hand-edited, truncated, or from an incompatible Codex CLI version; duplicate email labels colliding with existing accounts; corrupted files copied from another machine.","solutions":["Inspect the per-file error text logged next to the label to see why upsert_account_with_hints rejected the token file","Re-export or re-login that account in Codex CLI to obtain a fresh valid auth file (containing access_token/id_token or refresh_token) and re-import","Check that the file is valid JSON with the expected token keys before importing","If storage is implicated but not detected as disk-full, verify free space and write permissions on the app data directory","Collect results from the returned failed list (CodexFileImportFailure) instead of expecting the whole call to fail"],"exampleFix":"// before: blind bulk import of every file in a folder\nimport_from_files(paths).await?;\n// after: pre-validate JSON token keys and skip invalid files\nfor p in paths {\n    let content = fs::read_to_string(&p)?;\n    let ok = serde_json::from_str::<serde_json::Value>(&content)\n        .map(|v| v.get(\"access_token\").is_some()\n            || v.get(\"accessToken\").is_some()\n            || v.get(\"refresh_token\").is_some())\n        .unwrap_or(false);\n    if !ok { log::warn!(\"skipping invalid token file: {}\", p.display()); continue; }\n}","handlingStrategy":"validation","validationCode":"fn is_importable_codex_file(path: &Path) -> bool {\n    std::fs::read_to_string(path).ok()\n        .and_then(|c| serde_json::from_str::<serde_json::Value>(&c).ok())\n        .map(|v| v.get(\"access_token\").is_some()\n            || v.get(\"accessToken\").is_some()\n            || v.get(\"refresh_token\").is_some())\n        .unwrap_or(false)\n}","typeGuard":"fn has_valid_tokens(v: &serde_json::Value) -> bool {\n    v.get(\"access_token\").and_then(|t| t.as_str()).map(|s| !s.is_empty()).unwrap_or(false)\n        || v.get(\"refresh_token\").and_then(|t| t.as_str()).map(|s| !s.is_empty()).unwrap_or(false)\n}","tryCatchPattern":"match import_from_files(paths).await {\n    Ok(result) => {\n        for f in &result.failed {  // Vec<CodexFileImportFailure>\n            eprintln!(\"账号 {} 导入失败: {}\", f.email, f.error);\n        }\n    }\n    Err(e) => eprintln!(\"导入中止: {e}\"),\n}","preventionTips":["Pre-validate every auth file for the required token keys before calling import_from_files","Keep auth files in their original format; do not hand-edit or redact token fields","Re-export auth files from the same Codex CLI version used for import","Treat the returned `failed` list as the source of truth for which accounts need attention"],"tags":["codex","import","token-validation","batch-partial-failure"],"backgroundTag":"account-import-failed","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"}