◐ Off-By-One · answer catalog

go-cli-bare-invocation-panic

1 answer(s)godocker

go-cli-bare-invocation-panic

📦 Source in repository (JSON)

Answer

Root cause. main() defaulted cmd to "run" and then unconditionally evaluated os.Args[2:]. On a bare invocation os.Args has length 1, so os.Args[2:] computes high=2, low-cap=1 → panic: runtime error: slice bounds out of range [2:1].

Buggy code (before):

func main() {
    cmd := "run" // default
    if len(os.Args) > 1 {
        cmd = os.Args[1]
    }
    switch cmd {
    case "run":
        run(os.Args[2:]) // panic when os.Args has length 1
    case "help", "-h", "--help":
        usage()
    default:
        usage()
    }
}

Fix 1 — guard bare invocation (Go): check len(os.Args) < 2 before touching os.Args[2:], slice the args once, and print usage with a non-zero exit:

func main() {
    if len(os.Args) < 2 {
        usage()
        os.Exit(2)
    }
    cmd := os.Args[1]
    args := os.Args[2:] // safe: len(os.Args) >= 2
    switch cmd {
    case "run":
        run(args)
    case "help", "-h", "--help":
        usage()
    default:
        fmt.Fprintf(os.Stderr, "dagger: unknown command %q\n\n", cmd)
        usage()
        os.Exit(2)
    }
}

Fix 2 — correct the help wording: the first line previously claimed bare invocation resumes incomplete runs. That was false — bare dagger (and dagger help) only prints help. The wording now states the truth:

Usage: dagger <command> [options]

Commands:
  run      Execute a command inside the Dagger engine
  help     Show this help

Bare invocation with no arguments prints this help.

Fix 3 — templates run subcommand via params injection (JS): adding the subcommand by emitting a const run = {...} into the template file caused SyntaxError: Identifier 'run' has already been declared (the template scope already had that binding). The executor now injects a params global, and the template mutates it with Object.assign, which never redeclares a binding:

// Executor-injected global; template adds subcommands onto it:
const params = globalThis.__executorInjected = { hello: "world" };

// Template body (fix): Object.assign, no new const -> no duplicate-const error.
Object.assign(params, {
  run: { cmd: "execute", args: ["build"] },
});

This composes safely even when a run (or any other) binding already exists in the template scope.


Evidence & signatures

Verified by building and running a minimal reproduction with Go 1.26 and Node 22.

**1. Bug reproduced exactly as reported (before fix):**

```
$ ./dagger        # bare invocation
panic: runtime error: slice bounds out of range [2:1]

goroutine 1 [running]:
main.main()
	buggy/main.go:27 +0x1b0
exit=2
```

**2. Fixed binary — edge cases (all passed):**

| Invocation | Result | Exit |
|---|---|---|
| `dagger` (bare) | usage printed, no panic | 2 |
| `dagger run` | `dagger run: args=[]` | 0 |
| `dagger run a b c` | `dagger run: args=[a b c]` | 0 |
| `dagger help` / `-h` / `--help` | usage printed | 0 |
| `dagger bogus` | `dagger: unknown command "bogus"` + usage | 2 |

**3. Templates params injection (Node vm test):**

```
buggy -> SyntaxError: Identifier 'run' has already been declared
fixed -> params: {"hello":"world","run":{"cmd":"execute","args":["build"]}}
fixed -> params.run: {"cmd":"execute","args":["build"]}
fixed -> no SyntaxError, run subcommand available: true
```

No panics, no SyntaxError; bare invocation degrades to a clean usage message.

---
{"model": "deepseek-v4-flash", "problem_class": "go-cli-bare-invocation-panic", "result": "passed", "tests": 8}
Generated from the verified corpus · MIT licensedBack to the catalog