diff --git a/backend/src/tht/tht-runner.ts b/backend/src/tht/tht-runner.ts index c18b0caf..2568bf51 100644 --- a/backend/src/tht/tht-runner.ts +++ b/backend/src/tht/tht-runner.ts @@ -33,9 +33,18 @@ export class ThtRunner { return ["-c", this.cfg.configPath]; } + /** + * Build the full argv for a `tht` invocation. `--config`/`-c` is a PER-COMMAND + * option in the `tht` CLI (there is NO global `-c`), so it MUST be appended + * AFTER the subcommand + its flags, never prepended. + */ + buildArgv(args: string[], workspace?: string): string[] { + return [...args, ...this.configArg(workspace)]; + } + run(args: string[], workspace?: string): Promise<{ code: number; stdout: string; stderr: string }> { return new Promise((resolve) => { - const ch = spawn(this.cfg.thtBin, [...this.configArg(workspace), ...args], { + const ch = spawn(this.cfg.thtBin, this.buildArgv(args, workspace), { cwd: this.cfg.harnessDir, }); let stdout = ""; diff --git a/backend/test/tht-runner.test.ts b/backend/test/tht-runner.test.ts index 8c692f13..7e04a72a 100644 --- a/backend/test/tht-runner.test.ts +++ b/backend/test/tht-runner.test.ts @@ -37,7 +37,16 @@ test("sessionNew with missing workspace file falls back to default configPath ar await r.sessionNew({ question: "q", workspace: "foo" }); const [bin, argv] = (spawn as any).mock.calls[0]; expect(bin).toBe("tht"); - expect(argv).toEqual(["-c", "config/tht.yaml", "session", "new", "q", "--json"]); + // `--config`/`-c` is a PER-COMMAND option in tht (no global -c): it MUST follow + // the subcommand, never precede it. (Prepending it caused a live 500 "No such option: -c".) + expect(argv).toEqual(["session", "new", "q", "--json", "-c", "config/tht.yaml"]); +}); + +test("buildArgv appends -c AFTER the subcommand (never a global -c)", () => { + const r = new ThtRunner({ thtBin: "tht", harnessDir: "/nope", configPath: "config/tht.yaml" }); + expect(r.buildArgv(["session", "list", "--json"])).toEqual([ + "session", "list", "--json", "-c", "config/tht.yaml", + ]); }); test("sqlPreview argv has no positional file — uses --session to resolve path", async () => {