Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/checker-repo-path-guard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"review": patch
---

check-consumer-config: fail loudly when `--repo` names a path that does not exist. The flag takes a path to the consumer checkout, but an `owner/name` argument parses fine and resolves as a relative path, so every check reported missing and the report read as a catastrophically broken install instead of a typo (hit live during the 2026-08-20 rollout; the review-consumer-bump skill documents the footgun). The checker now throws naming the bad path. Also deduplicates parseArgs's twice-declared inline arg type into one `CliArgs` alias to stay under the max-lines cap the file already sits at.
29 changes: 29 additions & 0 deletions workflows/review/lib/check-consumer-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,35 @@ describe("leftover scaffolding", () => {
});
});

describe("the repo root", () => {
it("throws on a --repo that is not a path on disk", () => {
// `--repo Khan/webapp` parses fine and resolves as a relative path;
// without the guard every check reports missing instead of naming
// the typo.
expect(() => check({}, {repoRoot: "Khan/webapp"})).toThrow(
"--repo path does not exist: Khan/webapp",
);
});

it("accepts a root that exists and prefixes every path with it", () => {
const inputs = Object.fromEntries(
Object.entries(validInstall()).map(([path, content]) => [
`../consumer/${path}`,
content,
]),
);
const report = checkConsumerConfig(fakeFs(inputs), {
repoRoot: "../consumer",
checkerVersion: "1.11.0",
});
expect(codes(report, "error")).toEqual([]);
Comment thread
khan-actions-bot marked this conversation as resolved.
// Warnings too: warning-only checks (the .gitattributes lookup)
// also resolve through the prefixed root, and an error-only
// assertion cannot observe them.
expect(codes(report, "warning")).toEqual([]);
});
});

describe("parseArgs", () => {
it("reads the flags the CLI documents", () => {
expect(
Expand Down
26 changes: 13 additions & 13 deletions workflows/review/lib/check-consumer-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,13 @@ export const checkConsumerConfig = (
options: CheckOptions = {},
): ConsumerConfigReport => {
const repoRoot = options.repoRoot ?? ".";
// `--repo` takes a path to the consumer checkout; an owner/name arg
// resolves as a relative path and every check would report missing.
if (repoRoot !== "." && !fs.existsSync(repoRoot)) {
throw new Error(
`--repo path does not exist: ${repoRoot} (a path to the consumer checkout, not an owner/name)`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (non-blocking): Existence is a narrower predicate than the problem the description states. The description frames the problem as "the report read as a catastrophically broken install instead of a typo," but existence only separates typos that fail to resolve; an existing-but-wrong root reproduces the original symptom, and check-consumer-config-report.ts has no everything-is-missing summary to fall back on. Was the narrower predicate a deliberate scope choice, given the concrete owner/name footgun is fully covered by it?

review details found by first-principles

);
}
const at = (p: string): string =>
repoRoot === "." ? p : `${repoRoot}/${p}`;
const issues: ConfigIssue[] = [];
Expand Down Expand Up @@ -875,25 +882,18 @@ export {renderReport};
/* CLI */
/* -------------------------------------------------------------------------- */

/** Parse `--flag value` arguments. Unknown flags are an error, not ignored. */
export const parseArgs = (
argv: readonly string[],
): {
type CliArgs = {
repoRoot?: string;
filesFrom?: string;
explainPath?: string;
workflowPath?: string;
json: boolean;
strict: boolean;
} => {
const out = {json: false, strict: false} as {
repoRoot?: string;
filesFrom?: string;
explainPath?: string;
workflowPath?: string;
json: boolean;
strict: boolean;
};
};

/** Parse `--flag value` arguments. Unknown flags are an error, not ignored. */
export const parseArgs = (argv: readonly string[]): CliArgs => {
const out = {json: false, strict: false} as CliArgs;
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
switch (arg) {
Expand Down
Loading