Skip to content

fix(check): refuse paths outside the project, and name the missing paths it skips - #490

Merged
theCodeDrift merged 2 commits into
mainfrom
fix/475-check-outside-paths
Oct 12, 2026
Merged

theCodeDrift merged 2 commits into
mainfrom
fix/475-check-outside-paths

Conversation

@theCodeDrift

Copy link
Copy Markdown
Member

taskless check reported success: true with zero findings, and exited 0, for a path outside the project or one that does not exist. A check that never looked read exactly like a clean codebase.

Path passed to check Before Now
inside the project findings unchanged
absolute, outside success: true, exit 0 INVALID_INPUT, exit 1
directory, outside success: true, exit 0 INVALID_INPUT, exit 1
relative, outside (../elsewhere/bug.ts) success: true, exit 0 INVALID_INPUT, exit 1
outside and nonexistent (/no/such/file.ts) success: true, exit 0 INVALID_INPUT, exit 1
missing, inside the project success: true, silent success: true, exit 0, notice names the path

Choices

  • Outside the project is refused, not skipped. One outside path refuses the whole run, even alongside valid ones. Containment is decided from the path as written, so a nonexistent outside path is an error rather than a skip.
  • A missing path is still a success. The ci recipe pipes raw git diff --name-only output into check, which names deleted files, so a missing path is an ordinary input. It is now named in a notice (stderr in text, notices under --json), so "nothing scanned" reads differently from "clean".
  • Real path accepted too. Containment is checked against cwd and its real path. On macOS /tmp and /var are symlinks into /private/, so without this a hook handing over absolute paths in the other spelling would now hard-fail. Mutation-checked: removing the fallback turns the test red.
  • Existing INVALID_INPUT code, no new one.

Recipes

  • check → v8: path rules, the INVALID_INPUT row in Errors, and that an empty result alongside a skip notice means nothing was scanned.
  • ci → v5: "silently filters" becomes "skips, naming them in a notice", and run from the repository root.
  • hooks unchanged: every hook it describes passes paths inside the repository.

Tests

10 new cases in check.test.ts (each table row, mixed inside/outside, text mode, real-path spelling); the two existing missing-path tests now also assert the notice. Full CLI suite: 2183 passed.

Fixes #475

…ths it skips

check reported success with zero findings for a path outside the project or one that does not exist, so a check that never looked read as a clean codebase. A path outside the project is now refused with INVALID_INPUT and exit 1. A missing path is still skipped with exit 0, since raw git diff output names deleted files, but the skipped paths are named in a notice.
@theCodeDrift

Copy link
Copy Markdown
Member Author

@claude /review

@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @theCodeDrift's task in 2m 54s —— View job


Review mode: incremental — read 0 prior review thread(s) before reviewing.

.prior-review.json contained no review threads, no reviews, and no prior comments besides the trigger itself — this is the first review of this PR. Everything below is [New].

  • Read .prior-review.json
  • Fetch PR diff and changed files (gh pr diff, gh pr view --json)
  • Review check.ts logic (path containment, missing-path handling)
  • Review test coverage in check.test.ts
  • Review recipe docs (check.md, ci.md) and changeset
  • Post findings

Summary

The containment check is done the right way — relative(root, target) plus an escapes test, not a startsWith(root) prefix check, so a sibling directory with a shared prefix (e.g. project vs project2) isn't mistaken for "inside." The three-way split (kept/missing/outside) and its ordering (outside refuses the whole run before missing paths are even considered) match what the PR description promises, and the new tests exercise every row of the table in the PR body plus the mixed inside/outside and real-path cases. I didn't find anything I'd block on.

One asymmetry worth knowing about, not blocking:

[New] Symlink handling only normalizes cwd, not the target path — so the fix for "other spelling" only works in one direction. packages/cli/src/commands/check.ts lines 71–87: roots is built from resolvedCwd and, only if it differs, realpath(resolvedCwd). The target path (absolutePath) is never realpath'd. The case the new test covers (accepts an absolute path spelled through the project's real path) is: cwd given in symlink form (e.g. /tmp/x, from --dir or process.cwd()), target given in real form (e.g. /private/tmp/x/file.js) — that works because realCwd gets added to roots. The reverse — cwd already resolved to its real form (so realCwd === resolvedCwd, no second root added) while the target argument is spelled through the symlink (e.g. /tmp/x/file.js) — would still be rejected as "outside," since nothing realpath's the target for comparison. Low impact (a false "outside" rejection on a legitimate in-project path, not a traversal risk), and it's a narrower case than the one the PR explicitly set out to fix, so I'd treat this as a follow-up rather than something this PR needs to cover.

Everything else checked out:

  • relativeInside correctly handles the target === root case (empty-string relative path, not treated as escaping).
  • No double-printing of the missing-path notice: warnNotice no-ops under --json (notices go out once, via the final notices array), and in text mode the early-return branch and the fall-through branch are mutually exclusive, so text mode prints it exactly once too.
  • The "outside" early-return happens before requireCurrentSchema, so an outside path is refused even in an unmigrated/uninitialized project — reasonable, since it's a caller-input problem independent of project state.
  • Doc updates (check.md v8, ci.md v5) and the changeset accurately describe the new behavior; nothing oversold.

No inline comments posted — nothing met the bar for a blocking or actionable finding.

…s not read as outside

Only cwd was resolved to its real path. On macOS process.cwd() already returns the real form, so a path typed through /tmp still read as outside the project. The path's nearest existing ancestor is now resolved as well, which keeps a missing path comparable and judges an in-project symlink by where it sits.
@theCodeDrift

Copy link
Copy Markdown
Member Author

Re: @github-actions — "Claude finished @theCodeDrift's task in 2m 54s…"
#490 (comment)

Fixed in e1dfdb7 rather than deferred: the reverse case (cwd already in its real form, path typed through the symlink) is the more common one on macOS, since process.cwd() returns the real path. Each path is now also compared through the real path of its nearest existing ancestor, which keeps missing paths comparable and judges an in-project symlink by where it sits; a new test covers it and fails without the change.

— AI Coding Agent

@theCodeDrift
theCodeDrift merged commit 8a86506 into main Oct 12, 2026
4 checks passed
@theCodeDrift
theCodeDrift deleted the fix/475-check-outside-paths branch October 12, 2026 22:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check: a path outside the project, or one that doesn't exist, reports success with zero findings

1 participant