A raw ENOENT: no such file or directory, open 'path/to/file' stack trace tells the user the OS error code and the internal call stack — neither of which tells them what they should actually do. CWE-391 (Unchecked Error Condition) applies: the filesystem error is surfaced but not handled into a user-actionable message. When a CLI accepts user-specified file paths as arguments, it must own the responsibility of validating those paths before processing begins. Failing mid-processing — after partial output has already been written — is worse than failing fast at the boundary.
Low because the error is immediately visible to the user and does not propagate silently — it degrades UX but does not cause data corruption or security exposure.
Validate file existence at argument parse time, before any processing starts:
import { existsSync } from 'fs'
function validateFile(filePath: string): void {
if (!existsSync(filePath)) {
console.error(`Error: File not found: ${filePath}`)
console.error('Check the file path and try again.')
process.exit(2)
}
}
// call before any processing
validateFile(opts.input)
In click, use click.Path(exists=True) to get automatic validation with a clean error:
@cli.command()
@click.argument('file', type=click.Path(exists=True))
def lint(file):
"""Lint the specified FILE."""
# click errors before reaching here: "Error: Path 'missing.js' does not exist."
pass
The error message must name the specific path that was not found — generic "file not found" messages that omit the path force users to guess which argument was wrong.
ID: cli-quality.error-handling.file-not-found
Severity: low
What to look for: List all file system operations that accept user-specified paths. For each, check how the CLI handles missing file inputs. Look for commands that accept file paths as arguments or options. Verify that attempting to process a nonexistent file produces a clear error naming the file, not a raw ENOENT or stack trace. Check that the error is emitted before attempting to process the file (fail fast).
Pass criteria: Missing file inputs produce a user-friendly error message that names the specific file that wasn't found and suggests what to check. The error is caught and handled, not an unhandled ENOENT exception — 100% of file access operations must produce a clear error message when the file does not exist. Report: "X file operations found, all Y handle missing files gracefully."
Fail criteria: Missing file inputs produce raw system errors (ENOENT), stack traces, or generic errors that don't name the missing file.
Skip (N/A) when: The CLI does not accept file path inputs. All checks skip when no CLI entry point is detected.
Detail on fail: "Running 'mytool lint missing.js' produces raw 'Error: ENOENT: no such file or directory, open missing.js' instead of a friendly message" or "File existence is not checked before processing — error appears mid-processing with a stack trace"
Remediation: Validate file existence before processing:
// Node.js — check file existence first
import { existsSync } from 'fs'
function validateFile(filePath: string): void {
if (!existsSync(filePath)) {
console.error(`Error: File not found: ${filePath}`)
console.error('Check the file path and try again.')
process.exit(2)
}
}
# Python — click.Path handles this automatically
@cli.command()
@click.argument('file', type=click.Path(exists=True))
def lint(file):
"""Lint the specified FILE."""
pass # click will error: "Error: Path 'missing.js' does not exist."