{"record":{"id":"b42fdb94a00ebaa5","repo":"xai-org/grok-build","slug":"auth-entry-scope-key-not-found-while-persistin","errorCode":null,"errorMessage":"auth entry '{scope_key}' not found while persisting refreshed token","messagePattern":"auth entry '(.+?)' not found while persisting refreshed token","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-workspace/src/hub_auth/mod.rs","lineNumber":305,"sourceCode":"    // raced the shell's own refresh writer: whichever wrote second silently\n    // rolled back the other's freshly rotated refresh token on disk — a\n    // guaranteed future `invalid_grant` for every session sharing the file.\n    let Some(_lock) = lock_auth_file(path) else {\n        // The rotated token still serves this process from memory, so warn\n        // rather than fail — but disk now trails the IdP by one rotation, and\n        // a fresh process that picks it up will present a spent token.\n        tracing::warn!(\n            timeout = ?AUTH_LOCK_TIMEOUT,\n            \"auth.json.lock busy; skipping refreshed-token persist (disk left one rotation behind)\"\n        );\n        return Ok(());\n    };\n\n    let content = std::fs::read_to_string(path)?;\n    let mut raw: serde_json::Value = serde_json::from_str(&content)?;\n\n    let Some(obj) = raw.get_mut(scope_key).and_then(|e| e.as_object_mut()) else {\n        anyhow::bail!(\"auth entry '{scope_key}' not found while persisting refreshed token\");\n    };\n\n    // Never roll disk back to an older token. Each refresh persists on its own\n    // thread and a sibling shell writes the same file, so writes can arrive out\n    // of order; the loser would replace a live refresh token with a spent one\n    // and guarantee a future `invalid_grant`.\n    if let Some(new_expiry) = event.expires_at\n        && let Some(disk_expiry) = obj\n            .get(\"expires_at\")\n            .and_then(serde_json::Value::as_str)\n            .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())\n        && disk_expiry.with_timezone(&chrono::Utc) >= new_expiry\n    {\n        tracing::debug!(\"auth.json already holds a same-or-newer token; skipping persist\");\n        return Ok(());\n    }\n\n    obj.insert(","sourceCodeStart":287,"sourceCodeEnd":323,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-workspace/src/hub_auth/mod.rs#L287-L323","documentation":"write_refreshed_token in the hub auth module persists a refreshed OAuth token back into the shared auth.json file (JSON keyed by scope_key, e.g. per-scope entries). Before writing it takes a file lock, reads and parses auth.json, and looks up the top-level object entry named `scope_key`. This error is thrown when that entry is missing or is not a JSON object — i.e. the token was refreshed for a scope whose entry no longer exists on disk, so there is nowhere to persist the rotated access/refresh tokens.","triggerScenarios":"Calling write_refreshed_token (via persist_on_refresh / write_refreshed_token* test helpers) with a scope_key that is absent from auth.json; the auth.json file was recreated/reset or truncated by a sibling process so the scope entry vanished; the entry under scope_key exists but is not a JSON object (corrupted schema); or a typo'd/scope-mismatched key is passed while the refresh event belongs to a different scope.","commonSituations":"Concurrent shells sharing one auth.json where one process recreates the file without the other's scope entry; auth.json edited manually or rewritten by a newer/older tool version with a different schema; running with an auth file generated for a different scope key after a scope rename; a stale in-memory session attempting to persist after the file was replaced.","solutions":["Check auth.json actually contains a top-level object entry named `scope_key` before a refresh cycle; re-authenticate (full login) to regenerate the entry if it is missing","Recreate the missing entry via the library's initial auth/login flow rather than hand-editing, then retry the refresh persist","Back up and regenerate auth.json if it is corrupt or has a non-object value under scope_key; ensure all sibling processes use the same schema/version","Verify all concurrent shells/processes point at the same auth.json path and are the same tool version so no writer drops entries"],"exampleFix":"// before: auth.json lacks the scope entry after a sibling rewrite\nwrite_refreshed_token(&auth_path, \"workspace:rw\", &event)?;\n// after: ensure the entry exists before persisting\nensure_auth_entry(&auth_path, \"workspace:rw\")?; // re-login / seed from the refresh event if absent\nwrite_refreshed_token(&auth_path, \"workspace:rw\", &event)?;","handlingStrategy":"validation","validationCode":"fn auth_entry_exists(auth_path: &Path, scope_key: &str) -> bool {\n    std::fs::read_to_string(auth_path).ok()\n        .and_then(|c| serde_json::from_str::<serde_json::Value>(&c).ok())\n        .map(|v| v.get(scope_key).map_or(false, |e| e.is_object()))\n        .unwrap_or(false)\n}\n// call: if (!auth_entry_exists(&auth_path, \"workspace:rw\")) relogin();","typeGuard":"fn is_auth_entry(v: Option<&mut serde_json::Value>) -> Option<&mut serde_json::Map<String, serde_json::Value>> {\n    v.and_then(|e| e.as_object_mut())\n}","tryCatchPattern":"match write_refreshed_token(&auth_path, scope_key, &event) {\n    Ok(()) => {}\n    Err(e) if e.to_string().contains(\"not found while persisting refreshed token\") => {\n        // entry vanished (sibling rewrite / schema change): re-auth to regenerate it\n        relogin_and_seed_auth_entry(&auth_path, scope_key, &event)?;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Verify auth.json contains the expected scope_key object before starting refresh cycles","Do not hand-edit auth.json; use the library's login flow to create entries","Keep all shells/processes on the same tool version and same auth.json schema","Re-authenticate after any tool upgrade or scope rename instead of reusing the old file"],"tags":["auth","oauth","token-refresh","json","concurrency"],"backgroundTag":"auth-entry-missing","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}