Fast reference validator for codebases. Finds broken file references, fragile path patterns, and validates variable-based paths.
refcheck validates file references across your codebase, checking for:
- Broken source statements - Missing files in
sourcecommands (including variable paths like$SCRIPT_DIR/file.sh) - Broken script references - Missing files in
bashorshcommands - Old path patterns - Stale references after refactoring
- Fragile to working directory - Relative paths that only work from specific directories
- Fragile to refactoring - Variable assignments using
../traversal (breaks when files move)
Proactive error detection:
- Catch broken references before running expensive test suites
- Find issues in seconds vs minutes for full e2e tests
- Validate changes before committing
Refactoring safety:
- After moving files, verify all references updated
- Find stale patterns across entire codebase
- Custom pattern checking for any refactoring
Better than grep:
- Validates file existence, not just pattern matching
- Automatically filters false positives (docs, planning, dynamic paths)
- Structured output with suggestions
- Exit codes for CI/CD integration
Variable path validation:
- Resolves shell variables like
$SCRIPT_DIRand$DOTFILES_DIRbefore validation - Detects broken paths hidden behind variables
- Shows both original and resolved paths in error messages
- Gracefully skips unresolvable variables to avoid false positives
Warning system:
- Detects fragile patterns that may break in different contexts
- Configurable severity: warnings (default) or errors (--strict)
- Can be disabled for legacy codebases (--no-warn)
- Actionable suggestions for each warning
uv tool install https://github.com/datapointchris/refcheck.gitUpdate in place with refcheck --update, which installs the latest GitHub
release. Once a day, a run that is behind the latest release prints a one-line
notice to stderr; set NO_AUTO_UPDATE to silence it.
repos:
- repo: https://github.com/datapointchris/refcheck
rev: v0.2.1
hooks:
- id: refcheckAdd args: [--strict] to fail on warnings as well as errors.
The hook scans the whole repository rather than the staged files, and this is
deliberate: a reference breaks in the file that was not edited. Delete or move
b.sh and the stale source b.sh sits in a.sh, which is nowhere in the
changeset. Filtering to staged files would miss the entire class of bug the hook
exists to catch.
# Validate all references in current directory
refcheck
# Check specific directory
refcheck install/
# Find old pattern after refactoring
refcheck --pattern "old/path/" --desc "Update to new/path/"
# Filter by file type (like fd -e)
refcheck --type sh apps/
# Skip documentation files
refcheck --skip-docs
# Combine filters
refcheck --pattern "FooClass" --type py --skip-docs src/
# Disable warnings (only check for errors)
refcheck --no-warn
# Treat warnings as errors (strict mode for CI)
refcheck --strict# Moved tests/install/ to tests/install/
refcheck --pattern "tests/install/"
# Finds all stale references across repo# Quick validation (2 seconds vs 10+ minutes for e2e tests)
refcheck --skip-docs
# Catches broken references early# Validate install/ directory only
refcheck install/
# Check only shell scripts in apps/
refcheck apps/ --type sh# Strict mode - fail build on warnings
refcheck --strict
# Regular mode - warnings don't fail build
refcheck
# Disable warnings for legacy code
refcheck --no-warn# Find paths that only work from specific directories
refcheck # Shows warnings for fragile relative paths
# Find variable assignments using ../ traversal
refcheck # Shows warnings for SCRIPT_DIR="$(cd "$DIR/../../.." && pwd)"# Generate rules from git rename history (last 6 months by default)
refcheck --learn-rules
# Rules are stored per-repo at ~/.config/refcheck/repos/{repo-name}/rules.json
# These help suggest correct paths when references are brokenCreate ~/.config/refcheck/config.toml to customize behavior:
[learn]
time_window = "6 months" # How far back to analyze git history
[warnings]
stale_threshold = "7 days" # Warn when rules are older than this
show_no_rules_hint = true # Show hint to run --learn-rulesWhen errors found:
❌ Found 2 error(s)
Errors:
Broken Source (2):
────────────────────────────────────────────────────────────
tests/broken.sh:4
Missing: $SCRIPT_DIR/nonexistent.sh → /path/to/nonexistent.sh
→ Verify path exists or update reference
src/install.sh:15
Missing: /path/to/missing.sh
→ Verify path exists or update referenceWhen warnings found:
⚠️ Found 2 warning(s)
Warnings:
Fragile to Working Directory (1):
────────────────────────────────────────────────────────────
scripts/deploy.sh:3
Relative path only valid from: repo root
source tests/helpers.sh
→ Use root directory variable (e.g., $PROJECT_ROOT, $REPO_ROOT)
Fragile to Refactoring (1):
────────────────────────────────────────────────────────────
scripts/setup.sh:8
SCRIPT_DIR uses relative directory traversal (../) - fragile to file moves
→ Consider dynamic root detection: git rev-parse --show-toplevelWhen all valid:
✅ All file references valid
0- All references valid, or only warnings found (default mode)1- Found errors, or warnings in strict mode (--strict)
Use in scripts:
# Normal mode - warnings don't fail
if refcheck; then
echo "All references valid (warnings OK)"
fi
# Strict mode - warnings fail
if refcheck --strict; then
echo "All references valid (no errors or warnings)"
else
echo "Issues found, fix before deploying"
exit 1
fi| Flag | Description | Example |
|---|---|---|
path |
Directory to check (positional) | refcheck install/ |
--pattern PATTERN |
Find old pattern | --pattern "old/" |
--desc DESC |
Description for pattern | --desc "Now new/" |
--type, -t TYPE |
Filter by file type | --type sh |
--skip-docs |
Skip markdown files | --skip-docs |
--strict |
Treat warnings as errors (exit 1) | --strict |
--no-warn |
Disable fragile path warnings | --no-warn |
--learn-rules |
Generate rules from git history | --learn-rules |
--test-mode |
Include test fixtures (normally excluded) | --test-mode |
--update |
Install the latest release | --update --check |
--version |
Show the installed version | --version |
--help, -h |
Show help | --help |
Automatically excludes:
- Build artifacts:
.git,node_modules,.venv,__pycache__,site/ - Historical files:
.planning/,.claude/metrics/ - Dynamic paths: Container paths (
/root/,/home/), temp files (/tmp/) - Self-references: Usage examples in scripts referencing themselves
uv run pytestTests cover config parsing, rules management, file suggestions, and end-to-end CLI behavior. The suite also runs as a pre-commit hook.
Python 3.11+. The only runtime dependency is pyselfupdate; everything else is stdlib.
Modular structure:
cli.py- argparse CLI entry pointconfig.py- Config dataclass, TOML loadingchecker.py- ReferenceChecker class (core logic)rules.py- Rules loading/learning from gitsuggestions.py- File similarity matchingoutput.py- Result formattingselfupdate.py- Version reporting and release updates
vs grep:
grepfinds patterns but doesn't validate file existencerefcheckvalidates references point to real filesrefcheckauto-filters false positives
vs shellcheck:
shellcheckchecks literal paths in single filesrefcheckchecks across entire codebaserefcheckhandles dynamic paths and patterns
vs manual testing:
- Manual testing requires running full test suite (minutes)
refcheckvalidates in seconds- Catches issues before expensive CI/CD runs