Skip to content

Commit 62d6a81

Browse files
domenkozarclaude
andcommitted
fix: resolve bash path for exec probes during devenv test
Two issues prevented processes with exec readiness probes from working when pulled into the enterTest task graph (e.g. via `after = [ "devenv:processes:postgres" ]`): 1. run_tasks_with_roots() passed an empty string as the bash path, causing ExecProbe to fail with "No such file or directory" when trying to run Command::new(""). 2. After enterTest tasks completed, TasksUi::run() waited for processes to exit (run_foreground), blocking devenv test indefinitely. Fix (1) by calling get_bash_path() in run_tasks_with_roots(). Fix (2) by adding a stop_processes flag to run_tasks_with_ui/TasksUi so that processes started during task execution are stopped once all tasks complete. Closes #2713 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4f5c627 commit 62d6a81

7 files changed

Lines changed: 61 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- Fixed Ghostty shell integration not working in `devenv shell` by sourcing `ghostty.bash` from the rcfile when `GHOSTTY_RESOURCES_DIR` is set.
1616
- Fixed TUI panic when expanding error details containing multi-byte UTF-8 characters (e.g. miette underlines) by using character-based truncation instead of byte-based slicing.
1717
- Fixed `devenv processes stop` removing the process from the manager state, making it impossible to start or restart afterwards.
18+
- Fixed process exec probes failing with "No such file or directory" during `devenv test` when a task depends on a process (e.g. `after = [ "devenv:processes:postgres" ]`), because the bash path was not resolved for the enterTest task runner ([#2713](https://github.com/cachix/devenv/issues/2713)).
1819

1920
### Improvements
2021

devenv-tasks/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ async fn run_tasks(shutdown: Arc<Shutdown>) -> Result<()> {
199199

200200
// Run UI - processes events and waits for run_handle
201201
let ui = TasksUi::new(Arc::clone(&tasks), activity_rx, verbosity);
202-
let (status, _) = ui.run(run_handle).await?;
202+
let (status, _) = ui.run(run_handle, false).await?;
203203

204204
if shutdown.last_signal().is_some() {
205205
let _ = tasks.process_manager().stop_all().await;

devenv-tasks/src/ui.rs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,16 @@ impl TasksUi {
8181

8282
/// Run the UI, processing activity events until task runner completes.
8383
///
84-
/// If processes are still running after the task runner finishes, continues
85-
/// forwarding process output until all processes exit or shutdown is signaled.
84+
/// If `stop_processes` is false and processes are still running after the
85+
/// task runner finishes, continues forwarding process output until all
86+
/// processes exit or shutdown is signaled.
87+
///
88+
/// If `stop_processes` is true, any processes started during task execution
89+
/// are stopped once all tasks complete, so the caller regains control.
8690
pub async fn run(
8791
mut self,
8892
run_handle: tokio::task::JoinHandle<Outputs>,
93+
stop_processes: bool,
8994
) -> Result<(TasksStatus, Outputs), Error> {
9095
// Print header (unless quiet mode)
9196
if self.verbosity != VerbosityLevel::Quiet {
@@ -102,17 +107,23 @@ impl TasksUi {
102107
// Phase 2: If processes are still running (e.g., devenv-tasks invoked by
103108
// process-compose), keep forwarding output and wait for them to exit.
104109
if !self.tasks.process_manager.list().await.is_empty() {
105-
let cancel = self.tasks.shutdown.cancellation_token();
106-
let pm = Arc::clone(&self.tasks.process_manager);
107-
let fg_handle = tokio::spawn(async move { pm.run_foreground(cancel, None).await });
108-
109-
if let Err(e) = self
110-
.consume_events_until(fg_handle)
111-
.await
112-
.map_err(|e| format!("Process manager panicked: {e}"))
113-
.and_then(|r| r.map_err(|e| format!("Process manager error: {e}")))
114-
{
115-
return Err(Error::io(e));
110+
if stop_processes {
111+
// Stop processes so the caller regains control (e.g., enterTest
112+
// tasks that pulled processes into the task graph).
113+
let _ = self.tasks.process_manager.stop_all().await;
114+
} else {
115+
let cancel = self.tasks.shutdown.cancellation_token();
116+
let pm = Arc::clone(&self.tasks.process_manager);
117+
let fg_handle = tokio::spawn(async move { pm.run_foreground(cancel, None).await });
118+
119+
if let Err(e) = self
120+
.consume_events_until(fg_handle)
121+
.await
122+
.map_err(|e| format!("Process manager panicked: {e}"))
123+
.and_then(|r| r.map_err(|e| format!("Process manager error: {e}")))
124+
{
125+
return Err(Error::io(e));
126+
}
116127
}
117128
}
118129

devenv/src/devenv/container.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ impl Devenv {
9999
.build()
100100
.await?;
101101

102-
let (status, _outputs) = run_tasks_with_ui(tasks, verbosity, tui).await?;
102+
let (status, _outputs) = run_tasks_with_ui(tasks, verbosity, tui, true).await?;
103103

104104
if status.has_failures() {
105105
bail!("Failed to copy container");

devenv/src/devenv/mod.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -972,7 +972,7 @@ impl Devenv {
972972
.build()
973973
.await?;
974974

975-
let (status, outputs) = run_tasks_with_ui(tasks, verbosity, tui).await?;
975+
let (status, outputs) = run_tasks_with_ui(tasks, verbosity, tui, false).await?;
976976

977977
if status.has_failures() {
978978
miette::bail!("Some tasks failed");
@@ -1036,6 +1036,7 @@ impl Devenv {
10361036
verbosity: tasks::VerbosityLevel,
10371037
tui: bool,
10381038
) -> Result<(tasks::TasksStatus, BTreeMap<String, String>, Vec<String>)> {
1039+
let bash = self.get_bash_path().await?;
10391040
let config = tasks::Config {
10401041
roots,
10411042
tasks: task_configs,
@@ -1044,15 +1045,15 @@ impl Devenv {
10441045
cache_dir: self.devenv_state_dir(),
10451046
sudo_context: None,
10461047
env: envs,
1047-
bash: String::new(),
1048+
bash,
10481049
ignore_process_deps: false,
10491050
};
10501051

10511052
let tasks = Tasks::builder(config, verbosity, Arc::clone(&self.shutdown))
10521053
.build()
10531054
.await?;
10541055

1055-
let (status, outputs) = run_tasks_with_ui(tasks, verbosity, tui).await?;
1056+
let (status, outputs) = run_tasks_with_ui(tasks, verbosity, tui, true).await?;
10561057

10571058
let exports = outputs.collect_env_exports();
10581059
let messages = outputs.collect_messages();
@@ -2197,9 +2198,13 @@ async fn run_tasks_with_ui(
21972198
tasks: Tasks,
21982199
verbosity: tasks::VerbosityLevel,
21992200
tui: bool,
2201+
stop_processes: bool,
22002202
) -> Result<(tasks::TasksStatus, tasks::Outputs)> {
22012203
if tui {
22022204
let outputs = tasks.run(false).await;
2205+
if stop_processes {
2206+
let _ = tasks.process_manager().stop_all().await;
2207+
}
22032208
let status = tasks.get_completion_status().await;
22042209
Ok((status, outputs))
22052210
} else {
@@ -2212,7 +2217,7 @@ async fn run_tasks_with_ui(
22122217
let run_handle = tokio::spawn(async move { tasks_clone.run(false).await });
22132218

22142219
let ui = TasksUi::new(Arc::clone(&tasks), activity_rx, verbosity);
2215-
Ok(ui.run(run_handle).await?)
2220+
Ok(ui.run(run_handle, stop_processes).await?)
22162221
}
22172222
}
22182223

tests/tasks-in-test/.test.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,7 @@ test -z "${DEVENV_TEST_NOT_EXPORTED:-}"
2929
# enterTest-only task should have run (devenv test runs enterTest root,
3030
# which depends on enterShell)
3131
test "$DEVENV_TEST_ENTER_TEST_RAN" = "yes"
32+
33+
# Task depending on a process with exec readiness probe should have run.
34+
# Regression test for https://github.com/cachix/devenv/issues/2713
35+
test "$DEVENV_TEST_PROCESS_WAS_READY" = "yes"

tests/tasks-in-test/devenv.nix

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{ ... }:
1+
{ config, ... }:
22
{
33
# Task that exports an env var before enterShell.
44
# This tests that enterShell tasks run during `devenv test`
@@ -41,4 +41,24 @@
4141
exports = [ "DEVENV_TEST_ENTER_TEST_RAN" ];
4242
before = [ "devenv:enterTest" ];
4343
};
44+
45+
# Process with exec readiness probe. Tests that the bash path is resolved
46+
# for exec probes when processes are pulled into the enterTest task graph.
47+
# Regression test for https://github.com/cachix/devenv/issues/2713
48+
processes.probe-test = {
49+
exec = ''
50+
touch ${config.devenv.state}/probe-test-ready
51+
sleep 300
52+
'';
53+
ready.exec = "test -f ${config.devenv.state}/probe-test-ready";
54+
};
55+
56+
tasks."test:wait-for-process" = {
57+
exec = ''
58+
export DEVENV_TEST_PROCESS_WAS_READY="yes"
59+
'';
60+
exports = [ "DEVENV_TEST_PROCESS_WAS_READY" ];
61+
after = [ "devenv:processes:probe-test" ];
62+
before = [ "devenv:enterTest" ];
63+
};
4464
}

0 commit comments

Comments
 (0)