rtk-ai/rtk · error

sbt: no subcommand specified

Error message

sbt: no subcommand specified

What it means

rtk's `sbt` proxy requires at least one argument (task or subcommand) to route output through the correct filter (test, run, it:test, etc.). Invoking `rtk sbt` with no arguments leaves nothing to dispatch, so run_other bails with this message.

Solutions

  1. Pass an sbt task explicitly: `rtk sbt test`, `rtk sbt run`, `rtk sbt it:test`.
  2. Fix the invoking script/CI variable so the task name is actually set (guard `[ -n "$TASK" ]`).
  3. Use `rtk proxy sbt` to run sbt's interactive shell or any unfiltered invocation.
  4. Run `sbt` directly if you want its own REPL/usage behavior.

Example fix

// before
rtk sbt $TASK    # TASK empty -> sbt: no subcommand specified
// after
rtk sbt "${TASK:-test}"   # or fail fast: : "${TASK:?TASK must be set}"
Defensive patterns

Strategy: validation

Validate before calling

if [ -z "$TASK" ]; then echo "usage: rtk sbt <task> [args]" >&2; exit 2; fi
rtk sbt "$TASK" "$@"

Type guard

fn has_args(args: &[OsString]) -> bool { !args.is_empty() }

Try / catch

match rtk_result {
    Err(e) if e.to_string().contains("sbt: no subcommand") => {
        eprintln!("supply an sbt task, e.g. rtk sbt test");
        std::process::exit(2);
    }
    other => other?,
}

Prevention

When it happens

Trigger: Running `rtk sbt` with no task, or calling scala/sbt_cmd::run_other(&[], verbose) with an empty argument vector.

Common situations: CI scripts parameterized like `rtk sbt $TASK` where $TASK is empty; users who expect bare `rtk sbt` to drop into the sbt interactive shell; a Makefile target that lost its TASK argument.

Understand the failure class

Background: "no subcommand specified" and "... is required": CLI errors when a required argument is missing — this error's family across 13 libraries.

Related errors


AI-assisted analysis of rtk-ai/rtk@0924356b4c (2026-09-19). Data as JSON: /api/errors/0727a15b06376212. Report an issue: GitHub.

Appendix: source

Thrown at src/cmds/scala/sbt_cmd.rs:128

        RunOptions::with_tee(tee_label),
    )
}

pub fn run_test(args: &[String], verbose: u8) -> Result<i32> {
    run_task(args, "test", filter_sbt_test, "sbt_test", verbose)
}

pub fn run_compile(args: &[String], verbose: u8) -> Result<i32> {
    run_task(args, "compile", filter_sbt_compile, "sbt_compile", verbose)
}

pub fn run_run(args: &[String], verbose: u8) -> Result<i32> {
    run_task(args, "run", filter_sbt_run, "sbt_run", verbose)
}

pub fn run_other(args: &[OsString], verbose: u8) -> Result<i32> {
    if args.is_empty() {
        anyhow::bail!("sbt: no subcommand specified");
    }

    let subcommand = args[0].to_string_lossy().into_owned();

    // Integration test commands (it:test, IntegrationTest/test, etc.) produce standard
    // ScalaTest output — filter them like `sbt test`, through the shared runner so the
    // never_worse cap and tee hint apply (an unrecognized output would otherwise be
    // reprinted verbatim plus the hint, i.e. more than the raw command produced).
    if is_integration_test_cmd(&subcommand) || is_test_task(&subcommand) {
        let mut cmd = resolved_command("sbt");
        cmd.arg(&subcommand);
        for arg in &args[1..] {
            cmd.arg(arg);
        }

        if verbose > 0 {
            eprintln!("Running: sbt {} ...", subcommand);
        }

View on GitHub (pinned to 0924356b4c)