rtk-ai/rtk · error

sbt: no subcommand specified

Error message

sbt: no subcommand specified

What it means

RTK's sbt handler run_other requires a task name and bails on an empty args slice. Named tasks (compile, run, test variants) go through filtered runners; an empty invocation names no task and cannot be proxied to sbt meaningfully.

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 d977e1c316)

Solutions

  1. Pass a task: `rtk sbt compile`, `rtk sbt test`, or an integration variant like `rtk sbt it:test` (routed through the shared test filter)
  2. Use `rtk proxy sbt` for sbt's own interactive/bare behavior
  3. Assert non-empty task in scripts before invoking rtk

Example fix

# before
rtk sbt               # bail!("sbt: no subcommand specified")

# after
rtk sbt 'testOnly com.example.FooSpec'
rtk sbt it:test       # detected as integration test, filtered like sbt test
Defensive patterns

Strategy: validation

Validate before calling

bash:
if [ $# -eq 0 ]; then
  echo "usage: rtk sbt <task> [args...]" >&2
  exit 2
fi
rtk sbt "$@"

Type guard

rust:
fn has_sbt_task(args: &[std::ffi::OsString]) -> bool {
    !args.is_empty()
}

Try / catch

rust:
if let Err(e) = sbt_cmd::run_other(&args, verbose) {
    if e.to_string().contains("sbt: no subcommand specified") {
        eprintln!("usage: rtk sbt <task> (compile | test | it:test | ...)");
        std::process::exit(2);
    }
    return Err(e);
}

Prevention

When it happens

Trigger: Running `rtk sbt` with zero arguments (src/cmds/scala/sbt_cmd.rs:129). Also `rtk sbt $SBT_TASK` with SBT_TASK empty, or wrappers invoked without positionals.

Common situations: Build scripts parameterizing the sbt task that end up empty in some matrix leg; agents probing `rtk sbt` for a help screen; copy-pasted commands missing the task.

Related errors


AI-assisted analysis of rtk-ai/rtk@d977e1c316 (2026-08-16). Data as JSON: /api/errors/0727a15b06376212. Report an issue: GitHub.