Skip to content

Commit 77057de

Browse files
baonguyenNavaclaude
andcommitted
Handle preflight model-list transport failures
Wrap the auth+listModelIds preflight so a throw (e.g. a transient 5xx from Cursor.models.list after auth succeeds) returns EXIT.TRANSPORT with a clean JSON result instead of an unhandled rejection that crashed with no stdout. Add a CLI entrypoint .catch backstop. Found by the final whole-branch review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 812ac62 commit 77057de

3 files changed

Lines changed: 39 additions & 5 deletions

File tree

strata-qa/src/cli.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,5 +139,12 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
139139
main(process.argv.slice(2), {
140140
out: (s) => process.stdout.write(s),
141141
err: (s) => process.stderr.write(s),
142-
}).then((code) => process.exit(code));
142+
})
143+
.then((code) => process.exit(code))
144+
// Backstop: any unexpected throw still exits non-zero (never a silent unhandled
145+
// rejection), preserving the "operational failures exit non-zero" contract.
146+
.catch((e) => {
147+
process.stderr.write(`fatal: ${String(e)}\n`);
148+
process.exit(EXIT.TRANSPORT);
149+
});
143150
}

strata-qa/src/run.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,21 @@ describe("runQa", () => {
108108
expect(out.errorMessage).toContain("sonnet-4");
109109
});
110110

111+
test("listModelIds throws (post-auth transport failure) -> exit 7, status error, no crash", async () => {
112+
const root = makeDocsRoot();
113+
const out = await runQa(
114+
opts(root, join(root, "logs")),
115+
fakeSeam({
116+
listModelIds: async () => {
117+
throw new Error("models.list 503");
118+
},
119+
}),
120+
);
121+
expect(out.exitCode).toBe(EXIT.TRANSPORT);
122+
expect(out.result.status).toBe("error");
123+
expect(out.errorMessage).toContain("models.list 503");
124+
});
125+
111126
test("missing docs root files -> exit 4", async () => {
112127
const empty = mkdtempSync(join(tmpdir(), "strata-qa-empty-"));
113128
const out = await runQa(opts(empty, join(empty, "logs")), fakeSeam());

strata-qa/src/run.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,26 @@ export async function runQa(opts: RunOptions, seam: AgentSeam): Promise<RunOutco
106106
const logDir = opts.logDir ?? join(".logs", "qa");
107107

108108
// Preflight — fail loud with a distinct exit code per failure mode.
109-
if (!(await seam.checkAuth())) {
109+
// A THROW from the auth/list SDK calls (e.g. a transient 5xx after auth) maps
110+
// to TRANSPORT so the exit-code + single-JSON contract holds; checkAuth
111+
// returning false is the distinct AUTH case.
112+
let ids: string[];
113+
try {
114+
if (!(await seam.checkAuth())) {
115+
return {
116+
result: errorResult(model, "", null, null),
117+
exitCode: EXIT.AUTH,
118+
errorMessage: "CURSOR_API_KEY missing or failed to authenticate",
119+
};
120+
}
121+
ids = await seam.listModelIds();
122+
} catch (e) {
110123
return {
111124
result: errorResult(model, "", null, null),
112-
exitCode: EXIT.AUTH,
113-
errorMessage: "CURSOR_API_KEY missing or failed to authenticate",
125+
exitCode: EXIT.TRANSPORT,
126+
errorMessage: `preflight failed contacting the model API: ${String(e)}`,
114127
};
115128
}
116-
const ids = await seam.listModelIds();
117129
if (!ids.includes(model)) {
118130
return {
119131
result: errorResult(model, "", null, null),

0 commit comments

Comments
 (0)