Skip to content

fix(cli): validate that --cwd directory exists before execution#4658

Merged
escapedcat merged 3 commits intoconventional-changelog:masterfrom
omar-y-abdi:fix/4595-validate-cwd-exists
Mar 15, 2026
Merged

fix(cli): validate that --cwd directory exists before execution#4658
escapedcat merged 3 commits intoconventional-changelog:masterfrom
omar-y-abdi:fix/4595-validate-cwd-exists

Conversation

@omar-y-abdi
Copy link
Copy Markdown
Contributor

@omar-y-abdi omar-y-abdi commented Mar 14, 2026

Problem

Running commitlint -d doesNotExist silently returns exit code 0 with no output. The non-existent directory is passed to git operations which fail silently.

Fix

Added an fs.existsSync check after flag normalization in the main function. If the directory doesn't exist, a CliError is thrown with a clear message and exit code 1.

This works because the check runs before any git operations or config loading, so the user gets an immediate, actionable error instead of silent success.

Tests

  • --cwd with non-existent directory exits with code 1
  • -d alias behaves the same
  • --cwd with valid directory works normally
  • All 60 existing tests pass

Fixes #4595
Closes #4595

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Validate --cwd directory exists before git operations

🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Validate --cwd directory exists before execution
• Throw CliError with exit code 1 for non-existent directories
• Add three test cases covering both flag variants and valid paths
Diagram
flowchart LR
  A["User runs commitlint<br/>with --cwd flag"] --> B["normalizeFlags<br/>processes arguments"]
  B --> C{"fs.existsSync<br/>check directory"}
  C -->|"Directory exists"| D["Continue execution<br/>load config & lint"]
  C -->|"Directory missing"| E["Throw CliError<br/>exit code 1"]
  E --> F["Print error message<br/>to stderr"]
Loading

Grey Divider

File Changes

1. @commitlint/cli/src/cli.ts 🐞 Bug fix +10/-0

Add --cwd directory existence validation

• Import fs module from node:fs
• Add validation check after flag normalization in main function
• Throw CliError with descriptive message if --cwd directory doesn't exist
• Error is logged to console before throwing to ensure user sees the message

@commitlint/cli/src/cli.ts


2. @commitlint/cli/src/cli.test.ts 🧪 Tests +27/-0

Add tests for --cwd directory validation

• Add test for --cwd with non-existent directory (expects exit code 1 and error message)
• Add test for -d alias with non-existent directory (same behavior as --cwd)
• Add test for --cwd with valid existing directory (expects successful execution)

@commitlint/cli/src/cli.test.ts


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Mar 14, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. CWD file path accepted 🐞 Bug ✓ Correctness
Description
The new validation in main() only checks fs.existsSync(flags.cwd), so an existing file path passes
even though --cwd is documented as a directory. Subsequent git operations use nodeOptions.cwd and
can fail with a non-CliError, causing the CLI to throw (stack trace/uncontrolled exit) instead of
exiting cleanly with ExitCode.CommitlintErrorDefault.
Code

@commitlint/cli/src/cli.ts[R214-221]

+	if (!fs.existsSync(flags.cwd)) {
+		const err = new CliError(
+			`The specified --cwd directory "${flags.cwd}" does not exist.`,
+			pkg.name,
+		);
+		console.error(err.message);
+		throw err;
+	}
Evidence
--cwd is explicitly documented as a directory, but the new check only validates existence, not that
the path is a directory. The cwd value is passed into git execution via tinyexec (nodeOptions.cwd),
and non-CliError failures are rethrown by the CLI top-level handler rather than converted to a
controlled exit code, so a file-path cwd can still crash the process.

@commitlint/cli/src/cli.ts[60-66]
@commitlint/cli/src/cli.ts[214-221]
@commitlint/read/src/read.ts[30-35]
@commitlint/cli/src/cli.ts[170-176]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`--cwd` is documented as a directory, but current validation only checks `existsSync`, so existing *files* pass and later git operations can fail with a non-`CliError` (uncontrolled throw/stack trace).

### Issue Context
`main(cli.argv).catch(...)` only converts `CliError` (matching `pkg.name`) into a controlled exit; other errors are rethrown.

### Fix Focus Areas
- @commitlint/cli/src/cli.ts[214-221]
- @commitlint/cli/src/cli.ts[170-176]
- @commitlint/cli/src/cli.test.ts[41-66]

### Implementation notes
- Replace `existsSync` check with a `try { statSync(flags.cwd) }` guard and verify `.isDirectory()`.
- If stat fails or `.isDirectory()` is false, throw `new CliError(...)` with a message like: `The specified --cwd path &quot;...&quot; must be an existing directory.`
- Add a unit test that creates/uses an existing file in a fixture (e.g., `path.join(cwd, &#x27;commitlint.config.js&#x27; or &#x27;package.json&#x27;)`) and asserts stderr contains the message and exit code is `ExitCode.CommitlintErrorDefault`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@codesandbox-ci
Copy link
Copy Markdown

codesandbox-ci Bot commented Mar 14, 2026

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Comment thread @commitlint/cli/src/cli.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds early validation for the --cwd/-d CLI option so commitlint fails fast (with a non-zero exit code and actionable message) when given an invalid working directory, addressing #4595.

Changes:

  • Validate flags.cwd via fs.statSync() and throw a CliError when it’s missing or not a directory.
  • Add CLI integration tests for non-existent --cwd/-d, file-as-cwd, and valid directory behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
@commitlint/cli/src/cli.ts Adds early --cwd directory validation and emits a CliError before any git/config operations run.
@commitlint/cli/src/cli.test.ts Adds regression tests covering invalid/missing/valid --cwd and -d alias behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread @commitlint/cli/src/cli.ts Outdated
Comment thread @commitlint/cli/src/cli.ts
@escapedcat
Copy link
Copy Markdown
Member

Please have a look at the copilot feedback, thanks!

When a non-existent directory is passed via --cwd/-d, commitlint now
prints a clear error message and exits with code 1 instead of silently
returning exit code 0.

Closes conventional-changelog#4595
Replaces existsSync with statSync + isDirectory to reject file paths
that would cause uncontrolled errors downstream in git operations.
The catch-all handler for statSync failures reported all errors
(EACCES, EPERM, ENOTDIR) as "does not exist". Now checks
error.code and only uses that message for ENOENT; other errors
surface the actual errno code.
@omar-y-abdi omar-y-abdi force-pushed the fix/4595-validate-cwd-exists branch from 556a942 to 0444e42 Compare March 15, 2026 11:53
@omar-y-abdi
Copy link
Copy Markdown
Contributor Author

@escapedcat
Done

@escapedcat escapedcat merged commit cf80f75 into conventional-changelog:master Mar 15, 2026
12 checks passed
This was referenced Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

fix: passing a non existing working directory does nothing and returns with exit code 0

3 participants