affaan-m/ECC · error · anyhow::Error

Configured harness runner for

Error message

Configured harness runner for {runner_key} is missing a program

What it means

agent_program resolves the executable for a harness runner. If a custom harness runner is configured for runner_key but its program field is empty (or whitespace-only) after trimming, the library bails instead of silently falling back to the default binary. This catches malformed runner configuration that would otherwise produce a cryptic spawn failure.

Solutions

  1. Set the program field to the executable path (e.g., /usr/local/bin/claude) in the harness runner config
  2. Remove the runner config entry entirely so the library falls back to the built-in default (claude/codex/opencode/gemini)
  3. Validate the runner config at startup: trim and check non-empty program for every configured runner
  4. Ensure env-var interpolation used for program is not resolving to an empty string

Example fix

# before
cfg:
  harness_runner:
    claude-pro:
      program: ""
# after
cfg:
  harness_runner:
    claude-pro:
      program: "/usr/local/bin/claude"
Defensive patterns

Strategy: validation

Validate before calling

fn validate_runner_config(cfg: &Config) -> Result<(), String> {
    for (key, runner) in cfg.all_harness_runners() {
        if runner.program.trim().is_empty() {
            return Err(format!("harness runner '{key}' has empty program"));
        }
    }
    Ok(())
}

Type guard

fn has_program(r: &HarnessRunner) -> bool { !r.program.trim().is_empty() }

Try / catch

match launch(...) {
    Err(e) if e.to_string().contains("is missing a program") => {
        eprintln!("fix harness_runner config: set a non-empty program or remove the entry");
        Err(e)
    }
    other => other,
}

Prevention

When it happens

Trigger: cfg.harness_runner(&runner_key) returns Some(runner) whose runner.program.trim() is empty; e.g., config with an entry like [harness_runner.mykey] program = "".

Common situations: A partially filled config where a user created a runner section but left program blank; template/config generation writing empty strings for program; env var interpolation yielding empty string; copying a config template and forgetting to set program.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/c47897b8594acac6. Report an issue: GitHub.

Appendix: source

Thrown at ecc2/src/session/manager.rs:2335

            session.state
        );
    }

    if let Some(worktree) = session.worktree.as_ref() {
        let _ = crate::worktree::remove(worktree);
    }

    db.delete_session(&session.id)?;
    Ok(())
}

fn agent_program(cfg: &Config, agent_type: &str) -> Result<PathBuf> {
    let harness = HarnessKind::from_agent_type(agent_type);
    let runner_key = SessionHarnessInfo::runner_key(agent_type);
    if let Some(runner) = cfg.harness_runner(&runner_key) {
        let program = runner.program.trim();
        if program.is_empty() {
            anyhow::bail!("Configured harness runner for {runner_key} is missing a program");
        }
        return Ok(PathBuf::from(program));
    }

    match harness {
        HarnessKind::Claude => Ok(PathBuf::from("claude")),
        HarnessKind::Codex => Ok(PathBuf::from("codex")),
        HarnessKind::OpenCode => Ok(PathBuf::from("opencode")),
        HarnessKind::Gemini => Ok(PathBuf::from("gemini")),
        other => anyhow::bail!("Unsupported agent type: {other}"),
    }
}

fn resolve_session(db: &StateStore, id: &str) -> Result<Session> {
    let session = if id == "latest" {
        db.get_latest_session()?
    } else {
        db.get_session(id)?

View on GitHub (pinned to 8321021c54)