Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion docs/content/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,14 @@ On first run without shell integration, Worktrunk offers to install it. On first

All user config options can be overridden with environment variables using the `WORKTRUNK_` prefix.

### Precedence

Layer and specificity are separate axes. Layer decides which document supplies a key — system config, then user config, then `WORKTRUNK_` env vars, then `--config-set`. Specificity is applied to the merged result: a [user project-specific setting](@/config.md#user-project-specific-settings) beats the global key of the same name, whichever layer set each one.

So `WORKTRUNK_WORKTREE_PATH` overrides a global `worktree-path`, but a `[projects."github.com/owner/repo"]` entry that sets `worktree-path` still wins over it. To override a project entry for one invocation, name that entry — `wt config show` prints the identifier to use as the key:

{{ terminal(cmd="wt --config-set 'projects.__WT_QUOT__github.com/owner/repo__WT_QUOT__.worktree-path = __WT_QUOT__/tmp/scratch__WT_QUOT__' switch --create feature") }}

### Naming convention

Config keys use kebab-case (`worktree-path`), while env vars use SCREAMING_SNAKE_CASE (`WORKTRUNK_WORKTREE_PATH`). The conversion happens automatically.
Expand Down Expand Up @@ -675,7 +683,7 @@ Override the LLM command in CI to use a mock:

## Inline config overrides (`--config-set`)

`--config-set <toml>` overrides any user config key for a single invocation, with higher priority than both config files and `WORKTRUNK_` env vars. The value is a TOML fragment, so arrays and tables work directly; the flag is global (works before or after the subcommand), repeatable, and a later `--config-set` replaces an earlier one for the same key.
`--config-set <toml>` overrides any user config key for a single invocation, with higher priority than both config files and `WORKTRUNK_` env vars. The value is a TOML fragment, so arrays and tables work directly; the flag is global (works before or after the subcommand), repeatable, and a later `--config-set` replaces an earlier one for the same key. It is the highest layer, not the most specific key — overriding a `[projects."…"]` setting means naming that entry, per [the precedence rules](@/config.md#precedence).

{{ terminal(cmd="wt --config-set list.full=true list|||wt step copy-ignored --config-set 'step.copy-ignored.exclude=[__WT_QUOT__target__WT_QUOT__, __WT_QUOT__dist__WT_QUOT__]'") }}

Expand Down
12 changes: 11 additions & 1 deletion plugins/worktrunk/skills/worktrunk/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,16 @@ On first run without shell integration, Worktrunk offers to install it. On first

All user config options can be overridden with environment variables using the `WORKTRUNK_` prefix.

### Precedence

Layer and specificity are separate axes. Layer decides which document supplies a key — system config, then user config, then `WORKTRUNK_` env vars, then `--config-set`. Specificity is applied to the merged result: a [user project-specific setting](https://worktrunk.dev/config/#user-project-specific-settings) beats the global key of the same name, whichever layer set each one.

So `WORKTRUNK_WORKTREE_PATH` overrides a global `worktree-path`, but a `[projects."github.com/owner/repo"]` entry that sets `worktree-path` still wins over it. To override a project entry for one invocation, name that entry — `wt config show` prints the identifier to use as the key:

```bash
$ wt --config-set 'projects."github.com/owner/repo".worktree-path = "/tmp/scratch"' switch --create feature
```

### Naming convention

Config keys use kebab-case (`worktree-path`), while env vars use SCREAMING_SNAKE_CASE (`WORKTRUNK_WORKTREE_PATH`). The conversion happens automatically.
Expand Down Expand Up @@ -672,7 +682,7 @@ $ WORKTRUNK_COMMIT__GENERATION__COMMAND="echo 'test: automated commit'" wt merge

## Inline config overrides (`--config-set`)

`--config-set <toml>` overrides any user config key for a single invocation, with higher priority than both config files and `WORKTRUNK_` env vars. The value is a TOML fragment, so arrays and tables work directly; the flag is global (works before or after the subcommand), repeatable, and a later `--config-set` replaces an earlier one for the same key.
`--config-set <toml>` overrides any user config key for a single invocation, with higher priority than both config files and `WORKTRUNK_` env vars. The value is a TOML fragment, so arrays and tables work directly; the flag is global (works before or after the subcommand), repeatable, and a later `--config-set` replaces an earlier one for the same key. It is the highest layer, not the most specific key — overriding a `[projects."…"]` setting means naming that entry, per [the precedence rules](https://worktrunk.dev/config/#precedence).

```bash
$ wt --config-set list.full=true list
Expand Down
12 changes: 11 additions & 1 deletion skills/worktrunk/reference/config.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2522,6 +2522,16 @@ On first run without shell integration, Worktrunk offers to install it. On first

All user config options can be overridden with environment variables using the `WORKTRUNK_` prefix.

### Precedence

Layer and specificity are separate axes. Layer decides which document supplies a key — system config, then user config, then `WORKTRUNK_` env vars, then `--config-set`. Specificity is applied to the merged result: a [user project-specific setting](@/config.md#user-project-specific-settings) beats the global key of the same name, whichever layer set each one.

So `WORKTRUNK_WORKTREE_PATH` overrides a global `worktree-path`, but a `[projects."github.com/owner/repo"]` entry that sets `worktree-path` still wins over it. To override a project entry for one invocation, name that entry — `wt config show` prints the identifier to use as the key:

```console
$ wt --config-set 'projects."github.com/owner/repo".worktree-path = "/tmp/scratch"' switch --create feature
```

### Naming convention

Config keys use kebab-case (`worktree-path`), while env vars use SCREAMING_SNAKE_CASE (`WORKTRUNK_WORKTREE_PATH`). The conversion happens automatically.
Expand Down Expand Up @@ -2563,7 +2573,7 @@ $ WORKTRUNK_COMMIT__GENERATION__COMMAND="echo 'test: automated commit'" wt merge

## Inline config overrides (`--config-set`)

`--config-set <toml>` overrides any user config key for a single invocation, with higher priority than both config files and `WORKTRUNK_` env vars. The value is a TOML fragment, so arrays and tables work directly; the flag is global (works before or after the subcommand), repeatable, and a later `--config-set` replaces an earlier one for the same key.
`--config-set <toml>` overrides any user config key for a single invocation, with higher priority than both config files and `WORKTRUNK_` env vars. The value is a TOML fragment, so arrays and tables work directly; the flag is global (works before or after the subcommand), repeatable, and a later `--config-set` replaces an earlier one for the same key. It is the highest layer, not the most specific key — overriding a `[projects."…"]` setting means naming that entry, per [the precedence rules](@/config.md#precedence).

```console
$ wt --config-set list.full=true list
Expand Down
148 changes: 147 additions & 1 deletion tests/integration_tests/switch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use ansi_str::AnsiStr;
use insta_cmd::assert_cmd_snapshot;
use rstest::rstest;
use std::fs;
use std::path::Path;
use std::path::{Path, PathBuf};
use tempfile::TempDir;

// Snapshot helpers
Expand Down Expand Up @@ -2432,6 +2432,152 @@ worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}"
);
}

/// Layer and specificity are separate axes: `WORKTRUNK_WORKTREE_PATH` and
/// `--config-set worktree-path` set the *global* key, and a
/// `[projects."<id>"]` entry still beats the global key whichever layer set
/// it. Overriding a project entry means naming that entry.
///
/// Both halves are load-bearing, so each layer runs once with no project entry
/// present — proving it reaches `worktree-path` at all — before the project
/// entry is added and shown to outrank it.
///
/// Pins the precedence documented under "Environment variables → Precedence"
/// in the `wt config` help text (#3788).
#[rstest]
fn test_switch_create_project_worktree_path_outranks_invocation_layers(repo: TestRepo) {
set_github_remote_url(&repo);

let created_path = |args: &[&str], env: &[(&str, &str)]| {
let mut cmd = repo.wt_command();
cmd.args(args);
for (key, value) in env {
cmd.env(key, value);
}
let output = cmd.output().unwrap();
assert!(
output.status.success(),
"switch --create should succeed, stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
PathBuf::from(json["path"].as_str().unwrap())
};

// Controls, with no `[projects."…"]` entry in play: both invocation layers
// do reach `worktree-path`. Without these, the assertions below would hold
// equally if the env var and the global `--config-set` were ignored
// outright, so they would stop discriminating "applied to the global key,
// then lost on specificity" from "never applied at all".
repo.write_test_config("");
let env_only_path = created_path(
&[
"switch",
"--create",
"env-control",
"--format=json",
"--no-cd",
],
&[(
"WORKTRUNK_WORKTREE_PATH",
"{{ repo_path }}/../from-env-{{ branch | sanitize }}",
)],
);
assert_eq!(
env_only_path.file_name().unwrap(),
"from-env-env-control",
"WORKTRUNK_WORKTREE_PATH should set the global key, got {}",
env_only_path.display()
);

let cli_only_path = created_path(
&[
"--config-set",
r#"worktree-path = "{{ repo_path }}/../from-cli-{{ branch | sanitize }}""#,
"switch",
"--create",
"cli-control",
"--format=json",
"--no-cd",
],
&[],
);
assert_eq!(
cli_only_path.file_name().unwrap(),
"from-cli-cli-control",
"--config-set should set the global key, got {}",
cli_only_path.display()
);

repo.write_test_config(
r#"
[projects."github.com/owner/test-repo"]
worktree-path = "{{ repo_path }}/../from-project-{{ branch | sanitize }}"
"#,
);

// Env var sets the global key, so the project entry still wins.
let env_path = created_path(
&[
"switch",
"--create",
"env-layer",
"--format=json",
"--no-cd",
],
&[(
"WORKTRUNK_WORKTREE_PATH",
"{{ repo_path }}/../from-env-{{ branch | sanitize }}",
)],
);
assert_eq!(
env_path.file_name().unwrap(),
"from-project-env-layer",
"project entry should outrank WORKTRUNK_WORKTREE_PATH, got {}",
env_path.display()
);
Comment thread
worktrunk-bot marked this conversation as resolved.

// Same for `--config-set` on the global key — it is the highest layer,
// not the most specific key.
let cli_global_path = created_path(
&[
"--config-set",
r#"worktree-path = "{{ repo_path }}/../from-cli-{{ branch | sanitize }}""#,
"switch",
"--create",
"cli-global",
"--format=json",
"--no-cd",
],
&[],
);
assert_eq!(
cli_global_path.file_name().unwrap(),
"from-project-cli-global",
"project entry should outrank a global --config-set, got {}",
cli_global_path.display()
);

// Naming the project entry does override it.
let pinned_path = created_path(
&[
"--config-set",
r#"projects."github.com/owner/test-repo".worktree-path = "{{ repo_path }}/../from-pin-{{ branch | sanitize }}""#,
"switch",
"--create",
"pinned",
"--format=json",
"--no-cd",
],
&[],
);
assert_eq!(
pinned_path.file_name().unwrap(),
"from-pin-pinned",
"--config-set on the project entry should win, got {}",
pinned_path.display()
);
}

// ============================================================================
// PR Syntax Tests (pr:<number>)
// ============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ info:
WORKTRUNK_TEST_PARENT_SHELL: ""
WORKTRUNK_TEST_POWERSHELL_ENV: "0"
WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0"
WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000"
WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1"
WORKTRUNK_TEST_ZSH_INSTALLED: "0"
---
Expand Down Expand Up @@ -617,6 +618,14 @@ On first run without shell integration, Worktrunk offers to install it. On first

All user config options can be overridden with environment variables using the WORKTRUNK_ prefix.

Precedence

Layer and specificity are separate axes. Layer decides which document supplies a key — system config, then user config, then WORKTRUNK_ env vars, then --config-set. Specificity is applied to the merged result: a user project-specific setting beats the global key of the same name, whichever layer set each one.

So WORKTRUNK_WORKTREE_PATH overrides a global worktree-path, but a [projects."github.com/owner/repo"] entry that sets worktree-path still wins over it. To override a project entry for one invocation, name that entry — wt config show prints the identifier to use as the key:

  wt --config-set 'projects."github.com/owner/repo".worktree-path = "/tmp/scratch"' switch --create feature

Naming convention

Config keys use kebab-case (worktree-path), while env vars use SCREAMING_SNAKE_CASE (WORKTRUNK_WORKTREE_PATH). The conversion happens automatically.
Expand Down Expand Up @@ -656,7 +665,7 @@ Override the LLM command in CI to use a mock:

Inline config overrides (--config-set)

--config-set <toml> overrides any user config key for a single invocation, with higher priority than both config files and WORKTRUNK_ env vars. The value is a TOML fragment, so arrays and tables work directly; the flag is global (works before or after the subcommand), repeatable, and a later --config-set replaces an earlier one for the same key.
--config-set <toml> overrides any user config key for a single invocation, with higher priority than both config files and WORKTRUNK_ env vars. The value is a TOML fragment, so arrays and tables work directly; the flag is global (works before or after the subcommand), repeatable, and a later --config-set replaces an earlier one for the same key. It is the highest layer, not the most specific key — overriding a [projects."…"] setting means naming that entry, per the precedence rules.

  wt --config-set list.full=true list
  wt step copy-ignored --config-set 'step.copy-ignored.exclude=["target", "dist"]'
Expand Down
Loading