Smart concurrency control for validation runs
vibe-validate includes a smart locking system that prevents concurrent validation runs while allowing flexible configuration for different project needs.
Key features:
- Prevents duplicate validation runs by default
- Wait-for-completion mode for pre-commit hooks
- Project-scoped locking for shared resources (ports, databases)
- Directory-scoped locking for parallel worktrees
- Automatic project ID detection
By default, validation runs with:
- Locking enabled - Only one validation runs at a time
- Wait mode enabled - New runs wait for existing validation to complete
- Directory scope - Each working directory has its own lock (allows parallel worktrees)
# First terminal - starts validation
vibe-validate validate
# Second terminal - waits for first to complete
vibe-validate validate # Waits up to 5 minutes, then proceedsAdd locking configuration to your vibe-validate.config.yaml:
locking:
enabled: true # Default: true
concurrencyScope: directory # Options: "directory" (default) or "project"
projectId: my-app # Optional: auto-detected from git/package.json| Option | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Enable/disable locking entirely |
concurrencyScope |
'directory' | 'project' |
'directory' |
Lock scope strategy |
projectId |
string | auto-detected | Project identifier for project-scoped locks |
Each working directory gets its own lock file. Best for projects without shared resources.
Use when:
- Tests don't use fixed ports or shared databases
- Multiple git worktrees should validate independently
- No global state conflicts
Lock file: /tmp/vibe-validate-{encoded-directory-path}.lock
Example:
locking:
concurrencyScope: directory # DefaultBehavior:
cd ~/project/worktree-1
vibe-validate validate & # Runs immediately
cd ~/project/worktree-2
vibe-validate validate & # Runs in parallel (different lock)All directories for the same project share one lock. Best for projects with shared resources.
Use when:
- Tests use fixed ports (e.g.,
localhost:3000) - Integration tests use shared test databases
- Global state prevents concurrent runs
Lock file: /tmp/vibe-validate-project-{project-id}.lock
Example:
locking:
concurrencyScope: project
# projectId auto-detected from git remote or package.jsonBehavior:
cd ~/project/worktree-1
vibe-validate validate & # Runs immediately
cd ~/project/worktree-2
vibe-validate validate # Waits for worktree-1 to finish (same project)When using concurrencyScope: project, vibe-validate auto-detects the project ID:
-
Git remote URL (most reliable for worktrees/clones)
https://github.com/user/my-app.git→my-appgit@github.com:user/my-app.git→my-app- Supports GitHub, GitLab, Bitbucket
-
package.json name field (fallback)
"name": "my-app"→my-app"name": "@scope/my-app"→my-app(scope removed)
-
Error if neither is available
Explicitly set projectId to skip auto-detection:
locking:
concurrencyScope: project
projectId: my-custom-idIf you see: ERROR: concurrencyScope=project but projectId cannot be detected
Solutions:
- Add
locking.projectIdto your config - Ensure git remote is configured:
git remote -v - Ensure package.json has
namefield
Override locking behavior via command-line flags:
Disable locking for this run only (allows concurrent validations).
vibe-validate validate --no-lock &
vibe-validate validate --no-lock & # Both run concurrentlyUse cases:
- Testing/debugging
- CI environments with isolated containers
- Temporary override
Exit immediately if validation is already running (don't wait).
# Terminal 1
vibe-validate validate # Runs for 60 seconds
# Terminal 2
vibe-validate validate --no-wait # Exits immediately with code 0Use cases:
- Background hooks that shouldn't block
- Claude Code user-prompt-submit hooks
- Scripts that need fast failure
Maximum time to wait for running validation (default: 300 seconds = 5 minutes).
vibe-validate validate --wait-timeout 60 # Wait max 60 secondsUse cases:
- Shorter timeouts for CI
- Longer timeouts for slow test suites
--check→ automatically disables--lock(checking state doesn't need a lock)--no-lock→ allows concurrent validation runs (but still waits for running validations by default)--no-wait→ exits immediately if validation is running (use for background hooks)locking.enabled: falsein config → overrides--lockflag
Key insight: Locking and waiting are independent:
- Lock = whether to prevent concurrent runs (acquisition)
- Wait = whether to wait for existing runs (coordination)
- Use
--no-lockfor CI/parallel runs, use--no-waitfor non-blocking hooks
Scenario: JavaScript library with unit tests, no ports or databases.
Config: None needed (defaults work)
Behavior:
- Each directory has its own lock
- Multiple worktrees can validate in parallel
- No port/resource conflicts
# Multiple directories work independently
cd ~/my-lib/worktree-1 && vibe-validate validate &
cd ~/my-lib/worktree-2 && vibe-validate validate & # Runs in parallelScenario: Node.js API server, tests use localhost:3000.
Config:
locking:
concurrencyScope: project
# projectId auto-detected from git remoteBehavior:
- All directories share one lock
- Second validation waits for first
- No port conflicts
cd ~/my-api/worktree-1
vibe-validate validate & # Uses port 3000
cd ~/my-api/worktree-2
vibe-validate validate # Waits (port 3000 in use)Scenario: Integration tests use PostgreSQL test database.
Config:
locking:
concurrencyScope: project
projectId: my-monorepo # Explicit IDBehavior:
- Project-wide lock ensures sequential tests
- Database consistency maintained
Scenario: GitHub Actions, each job in separate container.
Config:
locking:
enabled: false # No need for locking in CIOr use CLI flag:
vibe-validate validate --no-lockBehavior:
- No locking overhead
- Each CI job validates independently
Scenario: Background validation on user prompt, shouldn't block.
Hook config (~/.claude/settings.json):
{
"hooks": {
"userPromptSubmit": {
"command": "vibe-validate validate --no-wait --yaml",
"description": "Run validation in background"
}
}
}Behavior:
- If validation running → exits immediately (code 0)
- If no validation → starts new validation
- Never blocks user
Directory-scoped:
/tmp/vibe-validate-_Users_jeff_project.lock
Project-scoped:
/tmp/vibe-validate-project-my-app.lock
{
"pid": 12345,
"directory": "/Users/jeff/project",
"treeHash": "abc123def456",
"startTime": "2025-10-25T12:00:00Z"
}- Stale locks (dead processes) are auto-removed
- Locks released when validation completes
- Crashes/kills cleaned up on next run
# List all locks
ls -la /tmp/vibe-validate*.lock
# View lock contents
cat /tmp/vibe-validate-project-my-app.lock
# Remove stale lock (if needed)
rm /tmp/vibe-validate*.lockSymptom: Always shows validation running, even when nothing is active.
Cause: Stale lock from crashed process.
Solution:
- Check if process actually running:
ps aux | grep vibe-validate - If not running:
rm /tmp/vibe-validate*.lock - Retry validation
Symptom: Error when using concurrencyScope: project.
Cause: No git remote or package.json name field.
Solution:
locking:
concurrencyScope: project
projectId: my-app # Explicit IDOr configure git remote:
git remote add origin https://github.com/user/my-app.gitSymptom: Validation starts before previous run finishes.
Cause: Test suite takes longer than 5 minutes.
Solution:
vibe-validate validate --wait-timeout 600 # 10 minutesOr in config (future enhancement):
locking:
waitTimeout: 600| Scenario | Overhead | Notes |
|---|---|---|
| Lock acquisition | ~1-5ms | Negligible |
| Wait mode (lock exists) | 0-300s | Depends on validation duration |
| No lock | 0ms | No overhead |
| Stale lock cleanup | ~10ms | Automatic |
vibe-validate never blocks on locking failures:
- Lock creation fails → proceed without lock
- Wait timeout reached → proceed with validation
- Stale lock detected → auto-cleanup and proceed
Developer workflow:
- Default behavior "just works"
- No configuration needed for common cases
- Explicit opt-out when needed
AI agent workflow:
--no-waitprevents blocking- Exit code 0 even when locked (no error noise)
- YAML output supports programmatic checks
No action needed - locking is enabled by default but won't change behavior unless you have:
- Multiple worktrees validating concurrently (now sequential by default)
- Tests using fixed ports (project scope recommended)
locking:
enabled: falseOr use --no-lock flag.
✅ Use directory scope when:
- Tests use random ports or no ports
- No shared databases or global state
- Multiple worktrees should validate independently
✅ Use project scope when:
- Tests use fixed ports (e.g., 3000, 8080)
- Integration tests use shared test database
- Global state prevents concurrent runs
- Worktrees should validate sequentially
✅ Disable locking when:
- CI environment with isolated containers
- Testing/debugging (temporary override)
- Absolutely sure no conflicts exist
- Short test suite (<60s): Keep default (300s)
- Medium test suite (60-300s): Keep default (300s)
- Long test suite (>300s): Increase timeout to 2x test duration
- CLI Reference - All CLI flags and commands
- Configuration Schema - Full config options
- Pre-Commit Workflow - Using locks in hooks
Planned improvements:
- Configurable wait timeout in YAML
- Lock status command (
vibe-validate lock-status) - Lock history/analytics
- Distributed locking (networked systems)