Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## 2.2.2 (unreleased)

### Bug Fixes

- Fixed allocated ports resolving to the declared base port in every command except `devenv up` and `devenv tasks run`. `devenv shell`, `direnv-export` and the other evaluating commands seed port allocations from a running process manager again ([#2710](https://github.com/cachix/devenv/issues/2710)).

## 2.2.1 (2026-08-02)

### Bug Fixes
Expand Down
24 changes: 19 additions & 5 deletions devenv/src/devenv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2902,12 +2902,26 @@ impl Devenv {

self.port_allocator.set_allow_in_use(false);

match processes::NativeProcessManager::api_request(
&self.native_socket_path(),
&processes::ApiRequest::Ports,
// Bounded because this runs before every command: a manager whose
// socket accepts but never answers must not hang the CLI.
let query = tokio::time::timeout(
std::time::Duration::from_secs(2),
processes::NativeProcessManager::api_request(
&self.native_socket_path(),
&processes::ApiRequest::Ports,
),
)
.await
{
.await;

let response = match query {
Ok(response) => response,
Err(_) => {
trace!("Timed out querying native manager for ports");
return;
}
};

match response {
Ok(processes::ApiResponse::PortAllocations { ports }) => {
let seeds: Vec<(String, String, u16)> = ports
.into_iter()
Expand Down
5 changes: 5 additions & 0 deletions devenv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,11 @@ async fn run_backend(

let devenv = Devenv::new(devenv_options).await?;

// Seed port allocations from a running process manager before anything
// evaluates, so allocated ports resolve for every command rather than only
// `up` and `tasks run`. No-op when no manager is running.
devenv.reserve_running_ports().await;

// PTY shell hands Devenv off to an owner task; we reclaim it after the session.
if use_pty && let Commands::Shell { cmd: None, args } = command {
// Pre-compute shell environment while we still own Devenv directly.
Expand Down
38 changes: 38 additions & 0 deletions tests/process-port-allocation-two-repos/.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,32 @@ PY
return 1
}

runtime_hash() {
local dotfile
dotfile="$(cd "$1" && pwd -P)/.devenv"
if command -v sha256sum >/dev/null 2>&1; then
printf '%s' "$dotfile" | sha256sum | cut -c1-7
else
printf '%s' "$dotfile" | shasum -a 256 | cut -c1-7
fi
}

wait_for_manager_pid() {
local repo=$1
# The manager publishes its PID file only after every process passes its
# readiness probe, so `up` returning a bound port does not yet mean the
# manager is discoverable. Commands that seed from it must wait for this.
local pid_file="${XDG_RUNTIME_DIR:-/tmp}/devenv-$(runtime_hash "$repo")/processes/native-manager.pid"
for _ in $(seq 1 300); do
if [ -s "$pid_file" ]; then
return 0
fi
sleep 0.1
done
echo "Timed out waiting for $pid_file"
return 1
}

start_repo() {
local repo=$1
rm -f "$repo/allocated-port" "$repo/server.pid" "$repo/up.log"
Expand Down Expand Up @@ -262,4 +288,16 @@ if [ "$repo1_process_port" = "$repo2_process_port" ]; then
exit 1
fi

# Commands other than `up` must resolve the allocated port too, instead of
# falling back to the base port that repo1 is holding.
wait_for_manager_pid repo2 || { cat repo2/up.log; exit 1; }
repo2_shell_port=$(cd repo2 && devenv --no-tui shell -- printenv ALLOCATED_PORT | tail -n 1 | tr -d '[:space:]')
echo "repo2 shell port: $repo2_shell_port"

if [ "$repo2_shell_port" != "$repo2_process_port" ]; then
echo "Expected devenv shell in repo2 to report its allocated port $repo2_process_port, got $repo2_shell_port"
cat repo2/up.log
exit 1
fi

echo "Two devenv projects with the same base port run concurrently."