Skip to content

feat(create): make a new worktree usable the moment it exists - #10

Open
murdore wants to merge 1 commit into
releasefrom
feat/run-repo-setup-script
Open

feat(create): make a new worktree usable the moment it exists#10
murdore wants to merge 1 commit into
releasefrom
feat/run-repo-setup-script

Conversation

@murdore

@murdore murdore commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Two gaps between "workforge created a worktree" and "you can work in it".


1. It was not necessarily runnable

Installing dependencies is not always enough. Some repos need a one-time bootstrap afterwards, and a worktree that skipped it fails in ways that do not name the missing step. In juspay/lighthouse the Playwright mock suite aborts with:

Error: Cannot find package '$models' imported from src/mocks/handlers.ts

That reads as a broken module alias, so you go looking at svelte.config.js and node_modules — not at a bootstrap you never ran.

Evidence

Reproduced on a fresh worktree before changing anything, then again after:

$models errors tests ran
fresh worktree, no setup 2 0
after node scripts/setup.js 0 83

Worth pre-empting the obvious objection: the synced .env is byte-identical either way — same 244 keys, same values, diffed. So "workforge already syncs .env" is not a reason to skip this; env syncing is not what closes the gap.

I could not pin the precise mechanism inside lighthouse's setup, and I would rather say so than invent one. The dependency itself reproduces in both directions, which is what this acts on.

Change

After a successful install, run <manager> run setup when package.json declares that script.

  • Conventional, not configurable. setup is the name the ecosystem already uses; a repo without it is completely unaffected.
  • Non-fatal. Failures warn and continue, exactly like the install step it follows.
  • Escapable. WORKFORGE_SKIP_SETUP=1.

It also returns early when install itself failed, rather than bootstrapping over a broken node_modules.


2. You still had to cd there yourself

New --switch / -s: opens a shell in the new worktree once it is ready.

The caveat is the feature's main design problem, so it is stated three times

This is a SUBSHELL. A process cannot change its parent shell's working directory, so no CLI flag can genuinely cd you anywhere. exit unwinds back to where you started — and someone expecting a real cd will read that unwind as the feature failing. So it is spelled out in the flag description, in the banner at spawn time, and in the code comment:

↪ Opening a subshell in /…/feat/BZ-123-thing
  Type 'exit' to return to /…/lighthouse

The only way to move the parent shell is a wrapper the user installs:

wf() { cd "$(workforge create "$@" --print-path)"; }

That is a different feature (it needs a --print-path mode and a documented shell function). This flag covers the common case where a subshell is enough.

Non-TTY

Skipped when either stdin or stdout is not a TTY. In CI or a piped invocation an interactive shell has nothing to read from and would hang the run rather than fail it — the worse of the two outcomes. It prints the cd line instead:

ℹ --switch ignored: not an interactive terminal.
ℹ   cd /…/chore/BZ-4139-switch-flag-test

The subshell also carries WORKFORGE_WORKSPACE and WORKFORGE_BRANCH, so a prompt or rc file can show the worktree without parsing the path.


Verified

✅ Dependencies installed successfully using pnpm
ℹ Running the repo's setup script (pnpm run setup)...
✅ Repo setup script completed
ℹ --switch ignored: not an interactive terminal.
  • Non-TTY guard proven — no hang.
  • Subshell cwd + env plumbing proven by spawning a shell with the same options and asserting pwd and both variables.
  • pnpm run build (tsc) clean.

Not verified: the interactive handover itself. It is not machine-testable from a non-TTY harness, so it wants one human run of workforge create -t feat -n x --switch before merge.

Note if you test this from a clone

The global workforge shim symlinks to the installed npm package, not to a local clone's dist/:

~/.nvm/…/bin/workforge -> ../lib/node_modules/@juspay/workforge/dist/index.js

So pnpm run build in a clone does not change what workforge runs. Invoke node /path/to/clone/dist/index.js directly — otherwise a working change looks like a no-op.

Two gaps between 'workforge created a worktree' and 'you can work in it'.

## It was not necessarily runnable

Installing dependencies is not always enough. Some repos need a one-time
bootstrap afterwards, and a worktree that skipped it fails in ways that do not
name the missing step: in juspay/lighthouse the Playwright mock suite aborts
with `Cannot find package '$models'`, which reads as a broken module alias and
sends you looking at svelte.config and node_modules instead of at a bootstrap
you never ran.

Reproduced on a fresh worktree before changing anything:

  no setup   -> 2 $models errors, 0 tests ran
  after      -> 0 errors, 83 tests ran

The synced .env is byte-identical either way — same 244 keys, same values — so
env syncing is not what closes the gap, and 'workforge already syncs .env' is
not a reason to skip this.

So: after install, run `<manager> run setup` when package.json declares it.
Conventional rather than configurable; a repo without that script is
unaffected. Failures warn and continue, exactly like the install step it
follows. WORKFORGE_SKIP_SETUP=1 opts out.

## You still had to cd there yourself

--switch (-s) opens a shell in the new worktree when it is ready.

Stated plainly in the flag description and again in the banner, because the
alternative is people reporting it as broken: this is a SUBSHELL. A process
cannot change its parent shell's working directory, so no flag can genuinely cd
you anywhere — `exit` unwinds back to where you started, and someone expecting
a real cd will read that unwind as a failure. The only way to move the parent
shell is a wrapper the user installs themselves:

  wf() { cd "$(workforge create "$@" --print-path)"; }

which is a different feature; this flag is for the common case where a subshell
is enough.

Skipped without a TTY — in CI or a piped run an interactive shell has nothing to
read from and would hang the run rather than fail it, which is the worse
outcome. It prints the cd line instead. The subshell also carries
WORKFORGE_WORKSPACE and WORKFORGE_BRANCH so a prompt can show the worktree
without parsing the path.

## Verified

  ✅ Dependencies installed successfully using pnpm
  ℹ Running the repo's setup script (pnpm run setup)...
  ✅ Repo setup script completed
  ℹ --switch ignored: not an interactive terminal.
  ℹ   cd /…/chore/BZ-4139-switch-flag-test

The non-TTY guard is proven (no hang), and the subshell's cwd/env plumbing is
proven by spawning a shell with the same options. The interactive handover
itself is not machine-testable from a non-TTY harness and wants a human to try
it once. tsc clean.
@murdore
murdore force-pushed the feat/run-repo-setup-script branch from 814d297 to df81bde Compare August 26, 2026 02:03
@murdore murdore changed the title feat(create): run the repo's setup script after installing dependencies feat(create): make a new worktree usable the moment it exists Aug 26, 2026
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.

1 participant