fix(freehire-search): reject --description-format on detail instead of ignoring it (#648)

* fix(freehire-search): reject --description-format on detail instead of ignoring it

The flag was on detail's known-flag list, but runDetail never reads it and
always prints the detail endpoint's HTML body stripped to plain text, so
detail <slug> --description-format markdown passed the unknown-flag guard,
exited 0 and printed text. Drop it from detail's list so it fails with
UNKNOWN_FLAG before any request; search keeps the flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changelog: place the #648 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
This commit is contained in:
authored and GitHub committed 2026-10-19 19:22:32 +02:00
1 parent 447fec0302
commit fc83b3bd53
3 files changed
+84 -2

No files matched your search

@@ -131,13 +131,15 @@ function parseIntFlag(name: string, raw: string | boolean | string[]): number |
// Long-form flag names each command accepts (parseFlags resolves the short // Long-form flag names each command accepts (parseFlags resolves the short
// aliases q/n to these before validation). "help"/"h" pass so `search --help` // aliases q/n to these before validation). "help"/"h" pass so `search --help`
// still prints usage. // still prints usage. detail takes no --description-format: it always prints
// the detail endpoint's HTML body stripped to text, so accepting the flag
// there ignored it silently.
const KNOWN_FLAGS: Record<string, Set<string>> = { const KNOWN_FLAGS: Record<string, Set<string>> = {
search: new Set([ search: new Set([
"query", "category", "city", "company", "country", "facet", "format", "jobage", "limit", "query", "category", "city", "company", "country", "facet", "format", "jobage", "limit",
"page", "region", "remote", "seniority", "skill", "description-format", "no-description", "help", "h", "page", "region", "remote", "seniority", "skill", "description-format", "no-description", "help", "h",
]), ]),
detail: new Set(["format", "description-format", "help", "h"]), detail: new Set(["format", "help", "h"]),
} }
async function main(): Promise<number> { async function main(): Promise<number> {
@@ -0,0 +1,72 @@
import { describe, expect, test } from "bun:test";
import { runCLI } from "./helpers";
// `detail` listed --description-format among its known flags, but runDetail
// never reads it: the detail endpoint's HTML body is always stripped to plain
// text. So `detail <slug> --description-format markdown` passed the
// unknown-flag guard, exited 0 and printed plain text - the silently ignored
// flag that guard exists to prevent. The CLI runs against a loopback API
// through its supported FREEHIRE_API_URL override; requests are counted.
const JOB = {
public_slug: "backend-engineer-acme-ab12cd34",
source: "greenhouse",
external_id: "1",
url: "https://boards.example/acme/1",
title: "Backend Engineer",
company: "Acme",
company_slug: "acme",
location: "Berlin, Germany",
description: "<p>Build things</p><ul><li>Go</li><li>Postgres</li></ul>",
skills: ["go"],
regions: ["eu"],
countries: ["DE"],
cities: ["Berlin"],
posted_at: "2026-10-01T00:00:00Z",
created_at: "2026-10-01T00:00:00Z",
enrichment: {},
};
async function detail(args: string[]) {
const requests: URL[] = [];
const server = Bun.serve({
hostname: "127.0.0.1",
port: 0,
fetch(request) {
requests.push(new URL(request.url));
return Response.json({ data: JOB });
},
});
try {
const result = await runCLI(["detail", JOB.public_slug, ...args], { FREEHIRE_API_URL: server.url.origin });
return { result, requests };
} finally {
server.stop(true);
}
}
describe("detail flags", () => {
for (const value of ["markdown", "text", "html"]) {
test(`--description-format ${value} is rejected as an unknown detail flag before any request`, async () => {
const { result, requests } = await detail(["--description-format", value]);
expect(result.exitCode).toBe(1);
expect(result.stdout).toBe("");
const error = JSON.parse(result.stderr);
expect(error.code).toBe("UNKNOWN_FLAG");
expect(error.error).toContain("--description-format");
expect(requests).toHaveLength(0);
});
}
test("--format plain still prints the detail", async () => {
const { result, requests } = await detail(["--format", "plain"]);
expect(result.exitCode).toBe(0);
expect(requests).toHaveLength(1);
expect(requests[0].pathname).toBe(`/api/v1/jobs/${JOB.public_slug}`);
expect(result.stdout).toContain("Backend Engineer");
});
test("search still validates its own --description-format", async () => {
const result = await runCLI(["search", "--description-format", "tekst"]);
expect(JSON.parse(result.stderr).code).toBe("BAD_ARG");
});
});
+8
View File
@@ -30,6 +30,14 @@ per-file diff commands.
handler-level test that pastes the search fixture's own apply URL into `detail` with fetch handler-level test that pastes the search fixture's own apply URL into `detail` with fetch
stubbed; the off-host cases fail on master. stubbed; the off-host cases fail on master.
- **`freehire-search detail` rejects `--description-format` instead of ignoring it**
(#648, `.agents/skills/freehire-search/cli/src/cli.ts`, `tests/detail-flags.test.ts`) - the flag
was on `detail`'s known-flag list, but `runDetail` never reads it and always prints the detail
endpoint's HTML body stripped to plain text. So `detail <slug> --description-format markdown`
passed the unknown-flag guard, exited 0 and printed text, which is the silently ignored flag
that guard exists to prevent. `detail` now answers it with `UNKNOWN_FLAG` before any request;
`search`, where the flag is documented and used, is unchanged.
## [1.7.3] - 2026-10-19 ## [1.7.3] - 2026-10-19
### Added ### Added