feat(pty): add cwd to PtySpawnOptions - #220
Open
sonhyrd wants to merge 1 commit into
Open
Conversation
|
@sondh0127 is attempting to deploy a commit to the Vercel Labs Team on Vercel. A member of the Team first needs to authorize it. |
A pty child always started in the app process's working directory: `PtySpawnOptions` took key/argv/cols/rows/term/on_event, `startRealPty` forwarded five of those to `pty.spawn`, and `pty.SpawnOptions` carried no directory either. For an interactive shell — which is what a pty child nearly always is — that is the one thing worth setting. The workaround it replaces is smuggling the change into the command line: `sh -c "cd '<path>' && exec <shell> -i"`. That turns a path into a shell word (a single quote in the path breaks it, so callers end up detecting that case and silently starting somewhere else), burns the shell's own `-c`, and has no Windows spelling worth trusting. Entered in the CHILD with `chdir` immediately before `execve`, not in the parent: the parent's directory is shared with every other spawn on the process, so a parent-side chdir would be a data race with them. That also keeps the failure honest — an unenterable directory reports through the same exec self-pipe a failed exec uses, so the app sees this spawn's `.exit` rather than a child running in the wrong place. `chdir` is async-signal-safe, so the post-fork section keeps the property its module comment states. On Windows the same request is `CreateProcessW`'s `lpCurrentDirectory`, which sets the child's directory without touching the parent's. Admission mirrors argv and TERM: empty, over `max_effect_pty_cwd_bytes`, or NUL-bearing paths refuse with the family's one rejected `.exit` — a path truncated at a NUL would silently enter a different directory than the caller named. `PtyRequest.cwd` exposes the request to the fake executor, where null and "" stay distinguishable.
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.
Summary
Adds
cwdtoPtySpawnOptions, forwarded throughpty.SpawnOptionsto achdirin the child (and toCreateProcessW'slpCurrentDirectoryon Windows).The gap
A pty child always started in the app process's working directory.
PtySpawnOptions(src/runtime/effects.zig:2890) tookkey,argv,cols,rows,term,on_event;startRealPty(:11025) forwarded five of those topty.spawn; andpty.SpawnOptions(src/runtime/pty.zig:392) carried no directory either. For an interactive shell — which is what a pty child nearly always is — that is the one thing worth setting.Why this matters
An app with several terminals open on different directories cannot express that at all. The workaround:
Three problems:
-c, so the child is a-cshell thatexecs an-ishell.cmd/powershellspelling worth trusting, so a Windows branch drops the directory entirely.Where the chdir happens, and why
In the child, immediately before
execve— not in the parent. The parent's working directory is shared with every other spawn on the process, so a parent-sidechdirwould be a data race with them. Entering it in the child also keeps the failure honest: an unenterable directory reports through the same exec self-pipe (pty.zig:601) a failed exec uses, so the app sees this spawn's.exitrather than a child running somewhere unexpected.chdiris async-signal-safe, so the post-fork section keeps the propertypty.zig:21states — "login_tty,dup2,close,execve, and_exit— no allocator, nosetenv". The path is NUL-terminated in the parent alongside the argv/envp arrays that are already built there.On Windows this is
CreateProcessW'slpCurrentDirectory, which sets the child's directory without touching the parent's.Admission
Mirrors argv and TERM: empty, over
max_effect_pty_cwd_bytes(1024), or NUL-bearing paths refuse with the family's one rejected.exit. The NUL rule matters most —chdirreads to the first NUL, so a truncated path would silently enter a different directory than the caller named.PtyRequest.cwdexposes the request to the fake executor, wherenull("inherit") and""stay distinguishable.Scope
fx.spawn'sSpawnOptionshas nocwdeither and this PR does not add one. It would follow the same shape if you want the symmetry; the evidence here is all on the pty side.argv[0]still resolves against PATH in the parent, before the child'schdir— documented on the field rather than changed, since resolving after the chdir would mean a post-fork PATH walk.Verification
zig build test: no new failures (this checkout has 6 pre-existingfailed commandlanes locally — codesign identity, packaging fixtures — identical before and after).desktop-runtime-core-testsrun directly: 531 passed, 11 skipped, 0 failed, including both new live-pty tests.zig fmt --checkclean.Tests
live pty cwd: the child starts in the requested directory— spawnssh -c pwdwith.cwd = "/"against the real transport and asserts the output./is not the test process's own directory, so a child that ignoredcwdfails rather than passing by coincidence.live pty cwd: an unenterable directory fails the spawn, never a child elsewhere— a nonexistent path produces zero output and a nonzero exit code, proving the failure reaches the app as this spawn's terminal.a fake pty mirrors the requested cwd, and null means inherit—pendingPtyAtreports the path, and a spawn without one reportsnullrather than"".pty admission: every refused spawn delivers exactly one rejected exit.