Hmbown/CodeWhale · error

refusing to push to a non-forge remote

Error message

refusing to push to a non-forge remote

What it means

push_branch validates the remote URL twice: it must be a syntactically valid git remote, and if it looks like a network remote (http(s)/ssh) it must classify as a known forge (GitHub/GitLab etc.). Pushing dispatch output to a non-forge network remote is refused.

Solutions

  1. Point the remote at a supported forge (github.com/gitlab.com etc.)
  2. Add the repository as a recognized forge if your version supports custom forge hosts
  3. Use an SSH/URL form the classifier accepts, or verify the URL spelling

Example fix

// before
push_branch(repo, "https://git.mycompany.internal/team/repo.git", branch)
// after
push_branch(repo, "https://github.com/team/repo.git", branch)
Defensive patterns

Strategy: validation

Validate before calling

if classify_url(&remote_url).is_none() {
    // remote is not a supported forge; reconfigure the remote
}

Prevention

When it happens

Trigger: push_branch receives a remote_url that looks like a network URL (https://git.example.com/...) but cloud_dispatch::classify_url cannot recognize it as a supported forge.

Common situations: Self-hosted or niche forges not in the classifier, push mirrors, generic git servers, or a mistyped URL whose host isn't a recognized forge domain.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/47fe3bf28904eb48. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/dispatch_runner.rs:531

    git(
        Some(&repo),
        &["am", "--quiet", "--3way", &patch_path.to_string_lossy()],
    )
    .context("the agent patch did not apply cleanly onto the target branch")?;
    git(Some(&repo), &["rev-parse", "HEAD"]).map(|out| out.trim().to_string())
}

/// Push the prepared branch. Plain push only — `--force` is never passed, so
/// an existing branch that is not a fast-forward fails closed instead of
/// rewriting the target's history.
fn push_branch(repo: &Path, remote_url: &str, branch: &str) -> Result<()> {
    let remote_url = cloud_dispatch::validate_git_remote_url(remote_url)?;
    if cloud_dispatch::is_forge_default_branch(branch) {
        bail!("refusing to push onto the forge default branch {branch}");
    }
    if looks_like_network_remote(&remote_url) && cloud_dispatch::classify_url(&remote_url).is_none()
    {
        bail!("refusing to push to a non-forge remote");
    }
    if remote_branch_exists(&remote_url, branch)? {
        bail!("refusing to update existing remote branch {branch}");
    }
    git(
        Some(repo),
        &[
            "push",
            "--quiet",
            "--",
            &remote_url,
            &format!("HEAD:refs/heads/{branch}"),
        ],
    )
    .map(|_| ())
    .context(
        "could not push the cloud agent branch (it may need credentials or the branch may have moved)",
    )

View on GitHub (pinned to 73e0f67d83)