From 1ee692cdb1d987f8a7ac71a1712b2d710b2756d6 Mon Sep 17 00:00:00 2001 From: mptyl Date: Sun, 28 Jun 2026 14:52:39 +0200 Subject: [PATCH] fix(backend): tht --config is per-command, append after subcommand (not global -c) Live end-to-end surfaced a 500 on every tht call: ThtRunner prepended '-c ' before the subcommand, but tht has no global -c option ('No such option: -c'). --config/-c is a per-command option, so it must be appended AFTER the subcommand. Extracted buildArgv() and fixed the unit test that had codified the wrong (prepended) order. Co-Authored-By: Claude Opus 4.8 --- backend/src/tht/tht-runner.ts | 11 ++++++++++- backend/test/tht-runner.test.ts | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) 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 () => {