Skip to content

Workspace Mode

prek supports a powerful workspace mode that allows you to manage multiple projects with their own pre-commit configurations within a single repository. This is particularly useful for monorepos or projects with complex directory structures.

Overview

A workspace is a directory structure that contains:

  • A root .pre-commit-config.yaml file
  • Zero or more nested .pre-commit-config.yaml files in subdirectories

Each directory containing a .pre-commit-config.yaml file is considered a project. Projects can be nested infinitely deep.

Discovery

When you run prek run without the --config option, prek automatically discovers the workspace:

  1. Find workspace root: Starting from the current working directory, prek walks up the directory tree until it finds a .pre-commit-config.yaml file. This becomes the workspace root.

  2. Discover all projects: From the workspace root, prek recursively searches all subdirectories for additional .pre-commit-config.yaml files. Each one becomes a separate project.

  3. Git repository boundary: The search stops at the git repository root (.git directory) to avoid including unrelated projects.

Note:

  • The workspace root is not necessarily the same as the git repository root, a workspace can exist within a subdirectory of a git repository.

  • The current working directory determines the workspace root discovery. prek starts searching from your current location and stops at the first .pre-commit-config.yaml file found while traversing up the directory tree. Running from different directories may discover different workspace roots. Use prek -C <dir> to change the working directory before execution.

  • Directories beginning with a dot (e.g. .hidden) are ignored during project discovery.

Project Organization

Example Structure

my-monorepo/
├── .pre-commit-config.yaml          # Workspace root config
├── .git/
├── docs/
│   └── .pre-commit-config.yaml      # Nested project
├── src/
│   ├── .pre-commit-config.yaml      # Nested project
│   └── backend/
│       └── .pre-commit-config.yaml  # Deeply nested project
└── frontend/
    └── .pre-commit-config.yaml      # Nested project

In this example:

  • my-monorepo/ is the workspace root
  • docs/, src/, src/backend/, and frontend/ are individual projects
  • Each project has its own .pre-commit-config.yaml file

Execution Model

File Collection

When running in workspace mode:

  1. Collect all files: prek collects all files within the workspace root directory
  2. Apply global filters: Files are filtered based on include/exclude patterns from the workspace root config
  3. Distribute to projects: Each project receives a subset of files based on its location

Hook Execution

For each project:

  1. Scope to project directory: Hooks run within their project's root directory
  2. Filter files: Only files within the project's directory tree are passed to its hooks
  3. Independent execution: Each project's hooks run independently with their own environment

Execution Order

Projects are executed from deepest to shallowest:

  1. src/backend/ (deepest)
  2. src/
  3. docs/
  4. frontend/
  5. my-monorepo/ (root, last)

This ensures that more specific configurations (deeper projects) take precedence over general ones.

Note: Files in subprojects will be processed multiple times - once for each project in the hierarchy that contains them. For example, a file in src/backend/ will be checked by hooks in src/backend/, then src/, then the workspace root.

Example Output

When running prek run on the example structure above, you might see output like this:

$ prek run
Running hooks for `src/backend`:
check python ast.........................................................Passed
check for merge conflicts................................................Passed
black....................................................................Passed
isort....................................................................Passed

Running hooks for `docs`:
Markdownlint.........................................(unimplemented yet)Skipped

Running hooks for `frontend`:
prettier.................................................................Passed

Running hooks for `src`:
isort....................................................................Passed
mypy.....................................................................Passed
check python ast.........................................................Passed
check docstring is first.................................................Passed

Running hooks for `.`:
fix end of files.........................................................Passed
check yaml...............................................................Passed
check for added large files..............................................Passed
trim trailing whitespace.................................................Passed
check for merge conflicts................................................Passed

Notice how:

  • Files in src/backend/ are processed by both the src/backend/ project and the src/ project
  • Each project runs in its own working directory
  • The workspace root processes all files in the entire workspace
  • Projects are executed from deepest to shallowest as described in the execution order

Command Line Usage

# Run from current directory, auto-discover workspace
prek run

# Run specific hook across all projects
prek run black

# Run from specific directory
cd src/backend && prek run

# Use -C option to change directory automatically
prek run -C src/backend

The -C <dir> or --cd <dir> option automatically changes to the specified directory before running, allowing you to target specific projects from any location in the workspace.

Note: When using prek install, only the workspace root configuration's default_install_hook_types will be honored. Nested project configurations are not considered during installation.

Project and Hook Selection

In workspace mode, you can selectively run hooks from specific projects or skip certain projects/hooks using flexible selector syntax.

Selector Syntax

The selector syntax has three different forms:

  1. <hook-id>: Matches all hooks with the given ID across all projects.
  2. <project-path>/: Matches all hooks from the specified project and its subprojects.
  3. <project-path>:<hook-id>: Matches only the specified hook from the specified project.

Selectors can be used to select specific hooks or projects, and combined with --skip to exclude certain hooks or projects.

Note: <project-path> can be a relative path, which is then resolved relative to the current working directory. Note that the trailing slash / in a <project-path> is important, if a selector does not contain a slash, it is interpreted as a hook ID.

Running Specific Hooks or Projects

# Run all hooks with a specific ID across all projects
prek run <hook-id>

# Run only hooks from a specific project
prek run <project-path>/

# Run only hooks with a specific ID from a specific project
prek run <project-path>:<hook-id>

Examples:

# Run all 'black' hooks across all projects
prek run black

# Run all hooks from the 'frontend' project
prek run frontend/

# Run only the 'lint' hook from the 'frontend' project
prek run frontend:lint

# Run the 'lint' from 'frontend' and 'black' from 'src/backend'
prek run frontend:lint src/backend:black

Skipping Projects or Hooks

You can skip specific projects or hooks using the --skip option, with the same syntax as for selecting projects or hooks.

# Skip all hooks from a specific project
prek run --skip <project-path>/

# Skip specific hooks within a selected project
prek run <project-path>/ --skip <subproject-path>/

# Skip all hooks with a specific ID across all projects
prek run --skip <hook-id>

Examples:

# Run all hooks except those from the 'frontend' project
prek run --skip frontend/

# Run hooks from 'frontend' but skip 'frontend/docs'
prek run frontend/ --skip frontend/docs

# Run hooks from 'frontend' but skip 'frontend/docs' and 'frontend:lint'
prek run frontend/ --skip frontend/docs --skip frontend:lint

# Run all hooks except 'black' and 'markdownlint' hooks
prek run --skip black --skip markdownlint

Note: Selecting a project includes all its subprojects unless explicitly skipped. Skipping a project also skips all its subprojects.

Note: The PREK_SKIP or SKIP environment variable can be used as an alternative to --skip. Multiple values should be comma-delimited:

# Skip 'frontend' and 'tests' projects
PREK_SKIP=frontend/,tests prek run

# Skip 'frontend/docs' project and 'src/backend:lint' hook
SKIP=frontend/docs,src/backend:lint prek run

Precedence rules for --skip command line options and environment variables are: --skip > PREK_SKIP > SKIP.

Advanced Examples

# Run 'lint' hooks from all projects except 'tests'
prek run lint --skip tests

# Run all hooks from 'src' and 'docs' but skip 'src/legacy'
prek run src/ docs/ --skip src/legacy

# Run 'format' hooks only from Python projects
prek run python:format

Single Config Mode

When you specify a configuration file using the -c or --config parameter, workspace mode is disabled and only the specified configuration file is used. This mode provides traditional pre-commit behavior similar to the original pre-commit tool.

In single config mode:

  • No workspace discovery: Only the explicitly specified configuration file is used
  • Single execution context: All hooks run from the git repository root directory
  • Global file scope: All files in the git repository are passed to all hooks
  • No project isolation: Hooks don't have access to project-specific working directories

Usage Examples

# Disable workspace mode, use specific config
prek run --config .pre-commit-config.yaml

# Use config from a subdirectory
prek run --config src/.pre-commit-config.yaml

# Short form using -c
prek run -c docs/.pre-commit-config.yaml

Key Differences: Workspace vs Single Config

Feature Workspace Mode Single Config Mode
Discovery Auto-discovers all .pre-commit-config.yaml files Uses single specified config file
Working Directory Uses workspace root Uses git repository root
File Scope All files in workspace All files in git repo
Hook Scope Project-specific file filtering All files pass to all hooks
Execution Context Each project runs in its own directory All hooks run from git root
Configuration Multiple configs Single config file only

Migration from Single Config

To migrate an existing single-config setup to workspace mode:

  1. Create workspace root: Move existing .pre-commit-config.yaml to repository root
  2. Add project configs: Create .pre-commit-config.yaml in subdirectories as needed
  3. Update file patterns: Adjust files/exclude patterns to be project-relative
  4. Test execution: Verify hooks run in correct directories with correct file sets

Workspace Cache

To improve performance in large monorepos, prek introduces a workspace cache mechanism. The workspace cache stores the results of project discovery, so repeated runs are much faster.

  • The cache is automatically used by default. You don't need to do anything for it to work.
  • If you make changes to .pre-commit-config.yaml files, remove projects, or otherwise change the workspace structure, prek will usually detect this and refresh the cache automatically.
  • If you add a new .pre-commit-config.yaml to your workspace, prek may not detect it immediately, try running with --refresh to ensure the cache is up to date.
prek run --refresh

This will clear and rebuild the workspace cache before running hooks.

Behavior Changes in Workspace Mode

When running in workspace mode, there are a few changes to the output format and behavior compared to single-config mode:

  1. Hook output is grouped by project, with a header indicating which project is currently running.
  2. Skipped hooks are not shown at all in the output, previously they were listed as "Skipped".

The workspace mode provides powerful organization capabilities while maintaining backward compatibility with existing single-config workflows.