xai-org/grok-build · error

Working tree is dirty. Commit changes first, or use --force.

Error message

Working tree is dirty. Commit changes first, or use --force.

What it means

Before creating a tag, `grok plugin tag` runs `git status --porcelain` in the plugin directory; if output is non-empty the working tree is dirty and the command bails, unless --force is passed. This prevents tagging uncommitted state.

Source

Thrown at crates/codegen/xai-grok-pager/src/plugin_cmd.rs:742

        Ok(ManifestLoadResult::NotFound) => bail!("No plugin.json found in {path}."),
        Err(e) => bail!("Failed to load manifest: {e}"),
    };

    let tag = format!(
        "v{}",
        version
            .strip_prefix('v')
            .or_else(|| version.strip_prefix('V'))
            .unwrap_or(&version)
    );

    if !force {
        let out = std::process::Command::new("git")
            .args(["status", "--porcelain"])
            .current_dir(&root)
            .output()?;
        if !out.stdout.is_empty() {
            bail!("Working tree is dirty. Commit changes first, or use --force.");
        }
    }

    if dry_run {
        println!("Would create tag: {tag}");
        if push {
            println!("Would push tag to remote.");
        }
        return Ok(());
    }

    let mut cmd = std::process::Command::new("git");
    cmd.args(["tag", &tag]);
    if force {
        cmd.arg("--force");
    }
    let out = cmd.current_dir(&root).output()?;
    if !out.status.success() {

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Commit or stash your changes, then re-run the tag command
  2. Re-run with `--force` to tag anyway
  3. Add untracked generated files to .gitignore so porcelain output is clean

Example fix

// before
grok plugin tag .
// after (either)
git add -A && git commit -m "release 1.0.0"
grok plugin tag .
// or
 grok plugin tag . --force
Defensive patterns

Strategy: validation

Validate before calling

fn worktree_clean(root: &std::path::Path) -> bool {
    std::process::Command::new("git")
        .args(["status", "--porcelain"])
        .current_dir(root)
        .output()
        .map(|o| o.stdout.is_empty())
        .unwrap_or(false)
}
// if !worktree_clean(&root) { commit/stash or pass --force }

Type guard

fn is_clean_repo(root: &std::path::Path) -> bool {
    root.join(".git").exists()
        && std::process::Command::new("git")
            .args(["status", "--porcelain"])
            .current_dir(root)
            .output()
            .map(|o| o.status.success() && o.stdout.is_empty())
            .unwrap_or(false)
}

Try / catch

match cmd_tag(dir, false, false, false) {
    Err(e) if e.to_string().contains("Working tree is dirty") => {
        eprintln!("Commit changes or re-run with --force");
    }
    Ok(_) => {}
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Running `grok plugin tag <dir>` without --force while the git repo in <dir> has uncommitted or untracked changes.

Common situations: Edited plugin files but forgot to commit; untracked generated files showing in porcelain; mid-development tagging attempt.

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/39cc75fda376ae33. Report an issue: GitHub.