Skip to content

feat(serve): use deterministic port when running inside a git worktree - #5140

Open
DavideCarvalho wants to merge 3 commits into
adonisjs:7.xfrom
DavideCarvalho:feat/worktree-port
Open

feat(serve): use deterministic port when running inside a git worktree#5140
DavideCarvalho wants to merge 3 commits into
adonisjs:7.xfrom
DavideCarvalho:feat/worktree-port

Conversation

@DavideCarvalho

@DavideCarvalho DavideCarvalho commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

When running node ace serve inside a git worktree, the development server now automatically uses a deterministic port derived from the worktree name, so multiple worktrees of the same application can run in parallel without port conflicts.

For example, running the same app in ~/worktrees/feature-login and ~/worktrees/feature-checkout will automatically bind to different ports, always the same for the same worktree.

Why this is needed

Working with multiple git worktrees of the same project is the standard way to develop several features/branches side by side. The problem: every worktree reads the same .env (or the default port 3333), so starting node ace serve in a second worktree fails with EADDRINUSE — you have to manually change the port (editing .env or exporting PORT) every time, and that change is not shared/predictable across teammates.

A deterministic port derived from the worktree name solves this without touching .env:

  • Each worktree always binds to the same port (stable hash), so the same command can be run over and over.
  • Two different worktrees get different ports, so they can run in parallel.
  • The behavior is automatic (no flags to remember) and opt-out via --no-worktree-port.

So the feature exists to answer: "start the dev server in this worktree, on a port that is unique to this worktree, without manual configuration."

How it works

  • The command detects if the application root is inside a linked git worktree by checking for a .git file (linked worktrees have a .git file pointing to the main repo, while the main checkout has a .git directory).
  • The worktree name is derived from the directory holding the .git file (walking up when the app lives in a nested directory).
  • The port is computed as basePort + (hash(worktreeName) % 1000), where basePort is read from the app dot-env files (PORT) and defaults to 3333. The hash is stable, so the same worktree always resolves to the same port.
  • The command sets process.env.PORT before starting the dev server — the assembler already prefers process.env.PORT, so no .env file is touched (git stays clean).

Usage

node ace serve              # auto-detects the worktree and picks a deterministic port
node ace serve --no-worktree-port  # disables the behavior

Changes

  • commands/serve.ts — worktree detection + port override + --no-worktree-port flag + help text
  • src/helpers/worktree.tsgetWorktreeName, getBasePort, computeWorktreePort
  • tests/helpers/worktree.spec.ts — unit tests for the helpers
  • tests/commands/serve.spec.ts — integration tests for the serve behavior

Notes

  • No new dependency: uses @adonisjs/env (already a dependency of core) and the built-in node:crypto/node:fs.
  • When the app is not inside a linked worktree (main checkout or non-git directory), the command behaves exactly as before.

@DavideCarvalho

Copy link
Copy Markdown
Contributor Author

Moved into #5136 to keep both features in a single PR.

@p3drosola

Copy link
Copy Markdown

Just pass PORT=1234 node ace serve it's up to your env harness script to calculate the right port. It shouldn't be a concern of the framework IMO

@DavideCarvalho

Copy link
Copy Markdown
Contributor Author

Just pass PORT=1234 node ace serve it's up to your env harness script to calculate the right port. It shouldn't be a concern of the framework IMO

Don't think so. In a AI pilled era, more tools a framework is able to give to AI models and harnesses the better.

Laravel even has a section of their docs about how Laravel can integrate with your AI tools.

Rails has an eval suite so it can benchmark which AI works better with the framework

NextJS cli is giving more and more cli commands so AI can get logs, stacktraces without dev intervention

That's just examples of where we're heading, and batteries included frameworks like Laravel and Rails are already going towards it

@thetutlage

Copy link
Copy Markdown
Member

Hello @DavideCarvalho

Thanks for the PR. Would you like to update the parts of this PR which uses the recently added helpers inside the @poppinss/utils package? https://github.com/poppinss/utils#getgitworktree

Also, I will prefer if we can also expose this command via the BaseCommand.

@p3drosola

p3drosola commented Sep 7, 2026

Copy link
Copy Markdown

@DavideCarvalho @thetutlage my concern has nothing to do with using AI tools. I just don't think that's behaviour that belongs in the framework it belongs in your development environment.

There are many different version control systems. Git is not the only one. It also goes against the logic of the 12 factor application whereby the confuguration should be injected in to the app, not the other way around.

If you want your work-trees to run a certain way you should should set them up that way. Not encode this logic in the framework.

The reason for that is that there are a million different ways to run the application. for example:

  • Are they all going to run different versions of the code and schema, but share a single DB ( and clobber eachother there) ? (In my case i clone a template database in each worktree and pass the new DB connection string, but it's not something i want the framework trying to do)
  • What other services are running on a system and possibly using ports? You have no way of knowing this ahead of time.

You shouldn't be trying to accomodate every possible way of running an application built on Adonis. That's the whole point of ENV vars.

There's nothing in this PR that can't be handled in one line of bash in your worktree setup script.

PORT="$(git rev-parse --absolute-git-dir | cksum | awk '{print 10000 + ($1 % 10000)}')"

@DavideCarvalho

Copy link
Copy Markdown
Contributor Author

Hello @DavideCarvalho

Thanks for the PR. Would you like to update the parts of this PR which uses the recently added helpers inside the @poppinss/utils package? https://github.com/poppinss/utils#getgitworktree

Also, I will prefer if we can also expose this command via the BaseCommand.

Updating it!

… port via BaseCommand

- Replace the hand-rolled linked worktree detection with the
  getGitWorktree helper added in @poppinss/utils 7.1.0
- Expose a getWorktreePort method on the BaseCommand (and ListCommand)
  so any command can resolve the deterministic worktree port
- Update the serve command to consume the BaseCommand method
- Setup real git worktrees inside the tests instead of faking the
  .git file, since getGitWorktree shells out to git

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@DavideCarvalho

Copy link
Copy Markdown
Contributor Author

@DavideCarvalho @thetutlage my concern has nothing to do with using AI tools. I just don't think that's behaviour that belongs in the framework it belongs in your development environment.

There are many different version control systems. Git is not the only one. It also goes against the logic of the 12 factor application whereby the confuguration should be injected in to the app, not the other way around.

If you want your work-trees to run a certain way you should should set them up that way. Not encode this logic in the framework.

The reason for that is that there are a million different ways to run the application. for example:

  • Are they all going to run different versions of the code and schema, but share a single DB ( and clobber eachother there) ? (In my case i clone a template database in each worktree and pass the new DB connection string, but it's not something i want the framework trying to do)
  • What other services are running on a system and possibly using ports? You have no way of knowing this ahead of time.

You shouldn't be trying to accomodate every possible way of running an application built on Adonis. That's the whole point of ENV vars.

There's nothing in this PR that can't be handled in one line of bash in your worktree setup script.

PORT="$(git rev-parse --absolute-git-dir | cksum | awk '{print 10000 + ($1 % 10000)}')"

Well a lot of things can be done with just a small bash script, what we have to think - specially the maintainers of adonis - is if we want the framework to have these small bash scripts built

About having different db or sharing a single db, I believe that depends on the dev on how it wants to setup the .env on the worktrees. If adonis had something like Laravel Sail this would be even easier because the sail could setup the environment based on the worktree as well

@DavideCarvalho

Copy link
Copy Markdown
Contributor Author

Thanks for the PR. Would you like to update the parts of this PR which uses the recently added helpers inside the @poppinss/utils package? https://github.com/poppinss/utils#getgitworktree

Also, I will prefer if we can also expose this command via the BaseCommand.

Done! 🙌

  • The manual worktree detection was replaced with getGitWorktree from @poppinss/utils (bumped to ^7.1.0). I kept the port hashing based on the worktree name (instead of the helper's path-based hash) so the same worktree name resolves to the same port across machines/teammates.
  • Added a getWorktreePort() method to the BaseCommand (and ListCommand), returning { worktree, port } | null, and the serve command now consumes it.
  • Since getGitWorktree shells out to git, the tests now create real git worktrees instead of faking the .git file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants