{"record":{"id":"5e94f45b36775823","repo":"gitbutlerapp/gitbutler","slug":"pre-push-hook-failed","errorCode":null,"errorMessage":"pre-push hook failed: {}","messagePattern":"pre-push hook failed: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-workspace/src/legacy/push.rs","lineNumber":103,"sourceCode":"                })?;\n        let before_sha = remote_before_sha(repo, remote_refname.as_ref())?;\n        let remote = repo.find_remote(remote_name.as_str())?;\n        let remote_url = remote\n            .url(gix::remote::Direction::Push)\n            .or_else(|| remote.url(gix::remote::Direction::Fetch))\n            .with_context(|| format!(\"Remote named {remote_name} didn't have a URL\"))?;\n\n        if run_hooks {\n            match hooks::pre_push(\n                repo,\n                &remote_name,\n                &remote_url.to_bstring().to_str_lossy(),\n                local_sha.id,\n                &RemoteRefname::from_str(&remote_refname.as_bstr().to_str_lossy())?,\n                run_husky_hooks,\n            )? {\n                hooks::HookResult::Success | hooks::HookResult::NotConfigured => Ok(()),\n                hooks::HookResult::Failure(error_data) => Err(anyhow::anyhow!(\n                    \"pre-push hook failed: {}\",\n                    error_data.error\n                )),\n            }?;\n        }\n\n        let gerrit_push_args = gerrit_push_args(\n            gerrit_mode,\n            local_sha.id,\n            target_branch_name.as_bstr(),\n            &push_opts,\n        );\n        let push_output = push_with_askpass(\n            repo,\n            local_sha.id,\n            remote_refname.as_ref(),\n            with_force,\n            force_push_protection && !skip_force_push_protection,","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-workspace/src/legacy/push.rs#L85-L121","documentation":"Thrown by but-workspace's legacy push path when a configured Git pre-push hook (or Husky-installed pre-push hook, when run_husky_hooks is on) exits non-zero, returning hooks::HookResult::Failure. The '{}' is the hook's captured stderr/error_data, so the message mirrors what the hook itself printed. The push is aborted before any ref update is attempted, exactly like `git push` being vetoed by the hook.","triggerScenarios":"Calling the workspace push API with run_hooks=true while .git/hooks/pre-push (or a husky .husky/pre-push) exists and exits non-zero — e.g. a linter/formatter/test step in the hook failing, or the hook script being missing its interpreter (env: ruby: No such file...).","commonSituations":"CI or local pre-push hooks running eslint/prettier/unit tests that fail; husky v4-style hooks still configured in package.json; a hook depending on tools not installed in the current shell (wrong PATH, nvm not loaded); pushing from an environment where the hook assumes a TTY.","solutions":["Read the text after 'pre-push hook failed:' — it is the hook's own output; fix whatever the hook reports (failing lint/test, missing tool).","Re-run the hook manually with the same arguments it would receive (remote name, URL, refline 'local_sha remote_refname') to reproduce outside the push.","If the hook is wrong or not wanted for this push, push with hooks disabled (the run_hooks=false / --no-verify equivalent) instead of deleting the hook.","For husky hooks, ensure husky is installed (`husky install`) and the .husky/pre-push file is executable and has a shebang."],"exampleFix":"# before: hook fails on unformatted code\n$ but push\nError: pre-push hook failed: eslint --staged found 2 problems\n\n# after: fix what the hook flags, then push again\n$ npx eslint --fix . && git add -A\n$ but push","handlingStrategy":"try-catch","validationCode":"// Rust: probe the hook before pushing\nlet hook = repo.path().join(\"hooks/pre-push\");\nif run_hooks && hook.exists() {\n    // expect it may fail; surface its output instead of a bare error\n}\n// or skip hooks entirely when the caller knows they are unwanted\nworkspace::legacy::push(/* run_hooks: false, run_husky_hooks: false */)","typeGuard":null,"tryCatchPattern":"match hooks::pre_push(...) {\n    Ok(HookResult::Failure(data)) => {\n        // treat as recoverable: show data.error, keep local commits intact\n        eprintln!(\"push vetoed by pre-push hook: {}\", data.error);\n    }\n    Ok(_) => { /* proceed with push */ }\n    Err(e) => return Err(e), // infrastructure error, not a hook veto\n}","preventionTips":["Keep pre-push hooks fast and dependency-light so they behave identically in every shell.","In automation, pass the existing run_hooks=false option rather than deleting hooks.","Test hooks by running them manually with the push refline arguments before relying on them."],"tags":["git","hooks","pre-push","push","husky"],"backgroundTag":"git-hook-rejected","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}