Repository navigation
fix: keep routes for commands that self-daemonize - #444
Merged
Merged
Conversation
When a wrapped command exits while its server keeps listening on the assigned port, portless removed the route, so the proxy returned 404 for a healthy server and the orphan held the port. On exit, wait up to EXIT_TIMEOUT_MS while processes from the command's process group are alive for a listener on the assigned port, then move the route to that listener's pid. The proxy keeps serving it, list shows it, and it drops once the server stops. Foreground exits still remove their routes right away. Fixes #411 Co-Authored-By: tiancheng-Xu <[email protected]>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…-daemon-routes # Conflicts: # packages/portless/src/cli-utils.ts # packages/portless/src/cli.ts
ctate
reviewed
Oct 12, 2026
ctate
requested changes
Oct 12, 2026
…ansfer, wait for detached daemons The proxy reloads routes when a cached owner has exited, so a reused port does not answer for the old hostname. A transferred route drops its ngrok and Tailscale URLs, which stop with portless. After a clean exit, portless waits up to EXIT_TIMEOUT_MS for a listener even when the process group is empty, since detached daemons leave it.
ctate
approved these changes
Oct 22, 2026
ctate
reviewed
Oct 22, 2026
…-daemon-routes # Conflicts: # packages/portless/src/cli.ts # skills/portless/SKILL.md
The proxy reloads routes.json on a debounce, so a request sent right after the command exits can arrive before the reload on fast Linux runners.
ctate
reviewed
Oct 30, 2026
Comment on lines
+398
to
+403
| // Tunnels and Serve config stop with the previous owner. | ||
| delete route.ngrokUrl; | ||
| delete route.ngrokPid; | ||
| delete route.tailscaleUrl; | ||
| delete route.tailscaleHttpsPort; | ||
| delete route.tailscaleFunnel; |
Collaborator
There was a problem hiding this comment.
When a self-daemonizing command runs with PORTLESS_CLOUDFLARE=1, the exit cleanup stops cloudflared and suppresses its exit handler, but this transfer leaves cloudflareUrl and cloudflarePid on the surviving route. portless list then continues advertising a dead public URL and retains the stopped tunnel PID.
… daemon Exit cleanup stops cloudflared, so the transferred route no longer advertises the dead URL or keeps the stopped tunnel PID.
Merged
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #411. Supersedes #413.
Problem
When a wrapped command self-daemonizes (starts a server, prints its address, and exits), portless removed the route. The proxy returned 404 for a healthy server, and the orphan kept holding the port. Astro 7 does this in agentic environments.
Change
EXIT_TIMEOUT_MSfor a listener on the assigned port, but only while processes from the command's process group are still alive. A plain foreground exit leaves the group empty and returns at once. Windows has no process group to watch, so it checks once.RouteStore.transferRoute). The proxy keeps serving it,portless listshows it, and it drops on its own once the server stops. The warning prints the PID to stop.This also fixes a race in the original report:
./fake-dev.shexits before the backgrounded server binds, so checking the port only at exit misses it.Based on #413 by @Tiancheng-Xu, credited as co-author, including the self-daemon e2e fixture.