feature: Add Job API + CLI support for setting the working directory from polyglot hooks#3968
Open
francoiscampbell wants to merge 1 commit into
Open
Conversation
Add a Job API endpoint, client method and `buildkite-agent workdir set` CLI command that let binary and polyglot (non-shell) hooks change the working directory for subsequent hooks and the command phase. The CLI resolves relative paths against the hook's actual working directory and validates existence; the endpoint accepts only absolute paths and records them as pending. The unwrapped hook runner consumes the pending workdir after the hook process exits and applies it via the existing applyEnvironmentChanges -> shell.Chdir path, so the change persists across later hooks via shell.wd. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
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.
Description
Today only wrapped POSIX shell hooks can change the working directory for subsequent hooks/commands — the hook wrapper exports
BUILDKITE_HOOK_WORKING_DIR=$PWDafter sourcing the hook and the executorchdirs to it.Binary and polyglot (non-shell) hooks can't do this. They run via
runUnwrappedHookin a child process, so anychdir()inside the child is lost.This PR gives unwrapped hooks a first-class way to change the working directory for subsequent phases, via a new Job API endpoint and a matching
buildkite-agent workdir set <path>CLI command.How it works:
PUT /workdir) accepts only absolute paths and records the request as pending (it does not stat the filesystem).shell.wdis single-writer, the endpoint only records the request. The executor consumes the pending workdir after the hook process exits and applies it through the existingapplyEnvironmentChanges→shell.Chdirpath — single-threaded, consistent with the wrapped-hook path.shell.wd; it is not reset between hooks.Scope is intentionally the unwrapped hook path. Wrapped POSIX shell hooks continue to use the wrapper's
$PWDcapture and are unaffected.Context
Internal Linear ticket (Buildkite).
Changes
PUT /api/current-job/v0/workdirendpoint (jobapi/workdir.go),WorkdirSetRequest/WorkdirSetResponsepayloads,Server.pendingWorkdir+ take-on-readTakePendingWorkdir, andClient.SetWorkdir.*jobapi.Serveron theExecutor;runUnwrappedHooknow consumes any pending workdir and applies it after the hook exits. Newhook.EnvChangesForWorkdirconstructor for workdir-only changes.workdirparent command with asetsubcommand (leaving room for a futureget).Testing
go test ./...). Buildkite employees may check this if the pipeline has run automatically.go tool gofumpt -extra -w .)New tests:
jobapi/workdir_test.go: absolute path → 200 + pending workdir set; relative → 422; empty → 422;TakePendingWorkdirreturns once then clears.clicommand/workdir_set_test.go: relative paths resolved against cwd; nonexistent path and non-directory both error before any API call.internal/job/integration/hooks_integration_test.go(TestBinaryHookCanSetWorkdir): a binarypre-commandhook sets the workdir via the Job API; asserts both thecommandandpost-commandhooks run in the requested directory, proving the change persists across hooks.Disclosures / Credits
Claude Code (Opus) implemented this change from a written plan I authored and reviewed, including the tests; I reviewed and verified the result.