run: support working directory argument and persist it across calls (CMDLINE-12, #166) - #249
Merged
Merged
Conversation
Contributor
Author
|
I have pushed an update with a few refinements and expanded test coverage:
Thank you very much for your time and guidance reviewing this. |
bbondy
previously approved these changes
Sep 12, 2026
…e root A vouched entry is keyed by resolved program path and exact argv, and records no directory at all. While the working directory was fixed at the workspace root that key was whole, because there was only one tree a line could run in. Now the planner chooses it, and both halves of what `a` grants leaked to a tree nobody was shown: the line ran unasked, and what it printed was labelled `(T,priv)`. `git log --oneline -50`, vouched for in a person's own repository and reissued with `directory` pointing at a vendored dependency, would have handed the planner commit messages an attacker wrote, as trusted content. The vouched road is open only where the line runs at the root, which is where every standing answer is spelled, and closed when no root is known for the same reason `read_proven` closes there. A line outside the root is asked about every time and its output is quarantined. Widening the key to include a directory would grant the shortcut back, and is not done: RUN-9 describes an entry as a program and its arguments, and the session record spells it that way. Three core tests state the two halves and the unknown-root case, and one turn test says the same thing end to end. The three tests that already covered the write and private-input precedence now name the root, since they put their plans at `/work` and would otherwise be passing for the new reason instead of theirs. Part of brave#166
…admit Two ways a proposed directory got through that should not have. It was checked against the `Read` rules, and a program's relative writes land in the directory it runs in, so a tree an `Edit` rule protects was not protected by the check that consulted only the read side: `npm install` in `vendor` writes throughout it without naming a file. It is an effect, and asks the effect rules. And a `directory` that was present but not a string was dropped rather than refused, which ran the line wherever the last call left off, the one place a planner that bothered to name a directory cannot have meant. `null` still reads as absent, because that is what filling an optional field in with nothing says. TOOL-1 names `directory` among the arguments that route, which is the table's statement of why either check exists. Part of brave#166
The line alone stopped saying where its output came from as soon as the directory began to carry across calls: the call that named it can have scrolled off the transcript or been summarised out of the conversation, and what reaches the person and the planner is the line. Both now read `cargo test (in vendor/dependency)` where the tree is not the workspace root, and are unchanged where it is. Structure either way, and a path this crate resolved itself, so nothing about what ran decides any of it. Part of brave#166
It moved before the run, so a line that was refused at the prompt, or that failed to start, still left the next line pointing at a tree the person had said no to. The background branch already waited for its launch to succeed; the foreground now carries it in the same place, once the stages have actually run. The test that covered this had its own copy of a `Confirmer` inline to answer two runs differently. That is `AskedAboutRuns::answering_in_turn`, which the shared one now offers and refuses anything past the end of the queue, so a test that runs a line more than it meant to fails rather than quietly approving it. Part of brave#166
The assertion was `contains("refused:") || contains("escapes")`, and the body it
reads carries the whole conversation including the call's own arguments, so the
second half held for the directory the test itself had named however the tool had
answered. Both halves are now required, the wording is the one the workspace error
actually prints, and the directory is named `../elsewhere` so nothing in the
request can satisfy the check on its own.
Part of brave#166
`resolve` was made `pub` to reach it from the tools module, which is in this crate already, so `pub(crate)` is what it needed. A resolver is the thing that decides whether a path escapes the workspace, and a caller outside the crate that resolved a path itself would be deciding that without the policy layer. Part of brave#166
…g is short of The clause named one test and five more were written against it. It also has two things to say now that a directory is a field a caller can set: that a directory other than the root is asked about every time and its output quarantined, which is RUN-8's consequence and the thing a reader of this clause alone would get wrong, and that the carrying lasts a turn rather than a session. The turn is where the second of those is decided, so it says so where the value is made, along with what it would take to carry one further: the session record and `--resume`, the way the vouched list is carried. Part of brave#166
bbondy
force-pushed
the
run-working-directory
branch
from
September 12, 2026 16:41
ac97f73 to
1b7e786
Compare
bbondy
approved these changes
Sep 12, 2026
Contributor
Author
|
Thanks a lot for the review and for improving the pr @bbondy I learned a lot from the changes, especially checking edge cases, security, permissions, and what happens when something fails. I’ll make sure to keep these things in mind and apply them in my next prs. appreciate it |
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.
Hello @bbondy,
This pull request implements
CMDLINE-12and resolves #166.Summary of Changes
Workspace::resolveso tools can resolve and confine workspace-relative and added directory paths.runtool schema and execution:directoryparameter to theruntool schema.directorythrough the policy layer, checking path confinement and deny rules.run_directoryto the workspace root at the start of each turn.run_directoryacross calls inTools, persisting the directory across calls once a plan is endorsed.tools.rs.the_working_directory_persists_across_callsincrates/agent/tests/turn.rs.docs/specs/tools/command-line.mdsoCMDLINE-12is verified by the new test.Please let me know if anything else is needed or if any changes would be preferred. Thank you very much.