Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ run = "echo common build ran"
EOF

# Create package config with includes pointing to common tasks
# The path is relative to the config file's directory (packages/foo/mise/)
# The path is relative to the config root (packages/foo/)
cat <<EOF >packages/foo/mise/config.toml
[task_config]
includes = ["../../../mise/common.tasks.toml"]
includes = ["../../mise/common.tasks.toml"]

[tasks.foo-task]
run = "echo foo-task ran"
Expand Down Expand Up @@ -99,7 +99,7 @@ mkdir -p packages/bar/src/mise
# Create a more deeply nested config with include going back multiple levels
cat <<EOF >packages/bar/src/mise/config.toml
[task_config]
includes = ["../../../../mise/common.tasks.toml"]
includes = ["../../../mise/common.tasks.toml"]

[tasks.bar-task]
run = "echo bar-task ran"
Expand Down
64 changes: 33 additions & 31 deletions e2e/tasks/test_task_monorepo_nested_config
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ run = "echo 'task from nested config'"
TOML

# Create tasks in the CUSTOM include path (specified in nested config)
# Includes are resolved relative to the config file's parent directory,
# so for .config/mise/config.toml, custom-tasks is at .config/mise/custom-tasks
mkdir -p projects/nested-config/.config/mise/custom-tasks
cat <<'SCRIPT' >projects/nested-config/.config/mise/custom-tasks/custom-task
# Includes are resolved relative to the config root (projects/nested-config/),
# so for .config/mise/config.toml, custom-tasks is at projects/nested-config/custom-tasks
mkdir -p projects/nested-config/custom-tasks
cat <<'SCRIPT' >projects/nested-config/custom-tasks/custom-task
#!/usr/bin/env bash
echo "task from custom includes"
SCRIPT
chmod +x projects/nested-config/.config/mise/custom-tasks/custom-task
chmod +x projects/nested-config/custom-tasks/custom-task

# Create tasks in the DEFAULT include path (.mise/tasks)
# These should NOT be loaded because the nested config exists
Expand Down Expand Up @@ -75,40 +75,41 @@ mkdir -p projects/nested-default/.config/mise

cat <<'TOML' >projects/nested-default/.config/mise/config.toml
# Nested config WITHOUT custom includes - should use default includes
# Default includes are resolved relative to config file's parent (.config/mise/)
# Default includes are resolved relative to the config root (projects/nested-default/)

[tasks.config-task2]
run = "echo 'task from nested-default config'"
TOML

# Create tasks in the DEFAULT include path, relative to the config file's parent
# For .config/mise/config.toml, the default ".mise/tasks" resolves to .config/mise/.mise/tasks
mkdir -p projects/nested-default/.config/mise/.mise/tasks
cat <<'SCRIPT' >projects/nested-default/.config/mise/.mise/tasks/default-include-task
# Create tasks in the DEFAULT include path, relative to the config root
# For .config/mise/config.toml with config_root=projects/nested-default/,
# the default ".mise/tasks" resolves to projects/nested-default/.mise/tasks
mkdir -p projects/nested-default/.mise/tasks
cat <<'SCRIPT' >projects/nested-default/.mise/tasks/default-include-task
#!/usr/bin/env bash
echo "task from default includes in nested config"
SCRIPT
chmod +x projects/nested-default/.config/mise/.mise/tasks/default-include-task
chmod +x projects/nested-default/.mise/tasks/default-include-task

# Also create tasks in the project-root-relative .mise/tasks
# These should NOT be loaded (default includes are relative to config file, not project root)
mkdir -p projects/nested-default/.mise/tasks
cat <<'SCRIPT' >projects/nested-default/.mise/tasks/wrong-location-task
# Also create tasks at the config-file-parent-relative path (.config/mise/.mise/tasks)
# These should NOT be loaded (default includes are relative to config root, not config file parent)
mkdir -p projects/nested-default/.config/mise/.mise/tasks
cat <<'SCRIPT' >projects/nested-default/.config/mise/.mise/tasks/wrong-location-task
#!/usr/bin/env bash
echo "task from wrong location - should NOT appear"
SCRIPT
chmod +x projects/nested-default/.mise/tasks/wrong-location-task
chmod +x projects/nested-default/.config/mise/.mise/tasks/wrong-location-task

# Test 5: Tasks from nested config using default includes should work
assert_contains "mise tasks --all" "//projects/nested-default:config-task2"
assert_contains "mise run '//projects/nested-default:config-task2'" "task from nested-default config"

# Test 6: Tasks from default include path (relative to config file) should be loaded
# Test 6: Tasks from default include path (relative to config root) should be loaded
assert_contains "mise tasks --all" "//projects/nested-default:default-include-task"
assert_contains "mise run '//projects/nested-default:default-include-task'" "task from default includes in nested config"

# Test 7: Tasks from project-root-relative .mise/tasks should NOT be loaded
# (because the nested config is detected and includes are relative to config file location)
# Test 7: Tasks from config-file-parent-relative .mise/tasks should NOT be loaded
# (because includes are relative to config root, not config file location)
assert_not_contains "mise tasks --all" "//projects/nested-default:wrong-location-task"

# ============================================================================
Expand All @@ -120,40 +121,41 @@ mkdir -p projects/mise-dir/.mise

cat <<'TOML' >projects/mise-dir/.mise/config.toml
# 1-level nested config at .mise/config.toml
# Custom includes pointing to "tasks" (relative to config parent .mise/)
# Custom includes pointing to "tasks" (relative to config root projects/mise-dir/)
[task_config]
includes = ["tasks"]

[tasks.mise-dir-task]
run = "echo 'task from .mise/config.toml'"
TOML

# Create tasks in the config-relative include path (.mise/tasks)
# This is "tasks" relative to config parent (.mise/)
mkdir -p projects/mise-dir/.mise/tasks
cat <<'SCRIPT' >projects/mise-dir/.mise/tasks/config-relative-task
# Create tasks in the config-root-relative include path (projects/mise-dir/tasks)
# This is "tasks" relative to config root (projects/mise-dir/)
mkdir -p projects/mise-dir/tasks
cat <<'SCRIPT' >projects/mise-dir/tasks/config-relative-task
#!/usr/bin/env bash
echo "task from config-relative include"
SCRIPT
chmod +x projects/mise-dir/.mise/tasks/config-relative-task
chmod +x projects/mise-dir/tasks/config-relative-task

# Create tasks in the subdir-relative default path (mise-tasks at project root)
mkdir -p projects/mise-dir/mise-tasks
cat <<'SCRIPT' >projects/mise-dir/mise-tasks/fallback-task
# Create tasks in the config-file-parent-relative path (.mise/tasks)
# These should NOT be loaded (includes resolve from config root, not config file parent)
mkdir -p projects/mise-dir/.mise/tasks
cat <<'SCRIPT' >projects/mise-dir/.mise/tasks/fallback-task
#!/usr/bin/env bash
echo "task from fallback - should NOT appear"
SCRIPT
chmod +x projects/mise-dir/mise-tasks/fallback-task
chmod +x projects/mise-dir/.mise/tasks/fallback-task

# Test 8: Tasks from 1-level nested config should be discovered
assert_contains "mise tasks --all" "//projects/mise-dir:mise-dir-task"
assert_contains "mise run '//projects/mise-dir:mise-dir-task'" "task from .mise/config.toml"

# Test 9: Tasks from config-relative includes should be loaded
# Test 9: Tasks from config-root-relative includes should be loaded
assert_contains "mise tasks --all" "//projects/mise-dir:config-relative-task"
assert_contains "mise run '//projects/mise-dir:config-relative-task'" "task from config-relative include"

# Test 10: Tasks from subdir-relative fallback path should NOT be loaded
# Test 10: Tasks from config-file-parent-relative path should NOT be loaded
assert_not_contains "mise tasks --all" "//projects/mise-dir:fallback-task"

# ============================================================================
Expand Down
9 changes: 4 additions & 5 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2243,13 +2243,13 @@ async fn load_file_tasks(

let mut tasks = vec![];
let config_root = Arc::new(config_root.to_path_buf());
let cf_dir = cf.get_path().parent().unwrap();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using .unwrap() here is risky and could lead to a panic if the config file is located at the root of the filesystem (e.g., /config.toml). In such a case, parent() would eventually return None, causing the unwrap() to fail.

The change to use cf.config_root() is a great improvement as it not only fixes the path resolution logic but also makes the code more robust by avoiding this potential panic.

let cf_root = cf.config_root();

for include in includes {
let paths = if include.starts_with("git::") {
vec![resolve_git_url_to_path(&include).await?]
} else {
expand_task_include(cf_dir, &include)
expand_task_include(&cf_root, &include)
};
for path in paths {
tasks.extend(load_tasks_includes(config, &path, &config_root).await?);
Expand All @@ -2268,9 +2268,8 @@ pub fn task_includes_for_dir(dir: &Path, config_files: &ConfigMap) -> Vec<PathBu
.rev()
.find_map(|cf| {
cf.task_config().includes.clone().map(|includes| {
// Resolve relative paths from the config file's directory, not the search directory
let cf_dir = cf.get_path().parent().unwrap_or(dir);
(includes, cf_dir.to_path_buf())
// Resolve relative paths from the config root, not the config file's directory
(includes, cf.config_root())
Comment on lines 2268 to +2272

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment immediately above this block still says includes are resolved relative to the config file’s directory, but the implementation now resolves relative to cf.config_root(). Update/remove the stale comment so it matches the new behavior to avoid future confusion.

Copilot uses AI. Check for mistakes.
})
})
.unwrap_or_else(|| {
Expand Down
Loading