{"record":{"id":"82353dda6ba5d678","repo":"gitbutlerapp/gitbutler","slug":"push-failed","errorCode":null,"errorMessage":"push failed","messagePattern":"push failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-agentlog/src/gitmeta/write.rs","lineNumber":298,"sourceCode":"    match gitmeta.pull(None) {\n        Ok(_) => {}\n        // An empty metadata remote has no ref yet; the push below initializes it.\n        Err(git_meta_lib::Error::GitCommand(message))\n            if message.contains(\"couldn't find remote ref\") => {}\n        Err(err) => return Err(err).context(\"failed to pull GitMeta metadata\"),\n    }\n\n    let mut attempts = 0;\n    loop {\n        attempts += 1;\n        let output = gitmeta\n            .push_once(None)\n            .context(\"failed to push GitMeta metadata\")?;\n        if output.success {\n            return Ok(());\n        }\n        if !output.non_fast_forward {\n            bail!(\"push failed\");\n        }\n        if attempts >= MAX_RETRIES {\n            bail!(\"push failed after {MAX_RETRIES} attempts\");\n        }\n        gitmeta\n            .resolve_push_conflict(None)\n            .context(\"failed to resolve GitMeta push conflict\")?;\n    }\n}\n\nfn stored_text(kind: RecordKind, text: Option<&str>) -> Option<String> {\n    let text = text?;\n    Some(match kind {\n        RecordKind::ToolResult => redact_text(cap_tool_result_text(text).as_ref()),\n        _ => redact_text(text),\n    })\n}\n","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-agentlog/src/gitmeta/write.rs#L280-L316","documentation":"After publishing session metadata, but-agentlog pushes the GitMeta ref in a retry loop (up to 5 attempts) that only retries non-fast-forward conflicts. This bail means `push_once` reported failure with `non_fast_forward == false` — the push was rejected for a reason other than a racing writer, so retrying the loop would not help. Typical causes are authentication, network, or remote-ref problems on the metadata remote.","triggerScenarios":"The final step of `but agentlog publish` (or sync) calling `gitmeta.push_once` and getting an unsuccessful output that is not a non-fast-forward rejection — e.g. expired credentials for the GitMeta remote, unreachable remote, or permission loss on the remote ref.","commonSituations":"Expired SSH key or credential helper for the metadata remote URL; VPN/network drop mid-publish; the GitMeta remote repository was deleted or made read-only; remote hosting auth changes (token revoked, SSO enforced).","solutions":["Check network access and credentials for the GitMeta remote (`git ls-remote <metadata-remote-url>`) and fix auth","Run `but agentlog sync` to confirm pull works, then retry publish — local state is intact, only the push failed","Verify the GitMeta remote repository still exists and the account has push rights","Re-run publish once auth is fixed; the record dedup (`:record-hashes`) prevents duplicates on the retry"],"exampleFix":"// before: single publish call surfaces the raw bail\npublish_session(&repo, &session)?;\n\n// after: retry publish after fixing auth, dedup makes it idempotent\nlet mut last_err = None;\nfor _ in 0..3 {\n    match publish_session(&repo, &session) {\n        Ok(()) => { last_err = None; break }\n        Err(e) if e.to_string().contains(\"push failed\") => { last_err = Some(e); continue }\n        Err(e) => return Err(e),\n    }\n}\nif let Some(e) = last_err { return Err(e.context(\"metadata push rejected; check GitMeta remote auth\")) }","handlingStrategy":"retry","validationCode":"// Verify the metadata remote is reachable and writable before publish\nlet ok = std::process::Command::new(\"git\")\n    .args([\"ls-remote\", &metadata_remote_url])\n    .output()\n    .map(|o| o.status.success())\n    .unwrap_or(false);\nif !ok { eprintln!(\"GitMeta remote unreachable or unauthorized\"); }","typeGuard":"fn push_output_is_retryable(output: &PushOutput) -> bool {\n    !output.success && output.non_fast_forward\n}","tryCatchPattern":"match publish(&repo, &session) {\n    Ok(()) => {}\n    Err(err) if err.to_string().contains(\"push failed\") => {\n        // fix auth/network, then retry: record dedup makes this idempotent\n        retry_after_auth_fix()\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Keep credentials for the GitMeta remote current (SSH agent / credential helper)","Run a preflight `git ls-remote` on the metadata remote before batch publishes","Treat a non-fast_forward push failure as permanent until auth/network is fixed — the retry loop will not help"],"tags":["gitmeta","push","network","auth","publish","rust"],"backgroundTag":"git-push-failed","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}