Skip to content

emrg: the shell tools tell the model to pass workdir, which the daemon replaces on every call #2698

Closed

Description

@pm25coder

What happens

Both shell tools' descriptions carry this sentence:

Each call is a fresh process — no state (working directory, variables, functions) persists between calls, so pass workdir instead of using cd.

The action it advises cannot work. In the daemon path EmrgServer._inject_tool_arguments writes the session's cwd into a shell call's workdir unconditionally — the if tc_name in SHELL_TOOL_NAMES: branch in emrg/server/daemon.py — and when a sandbox tier is configured it also writes workspace, which the executors read first (emrg/tools/bash_tool_v2.py, emrg/tools/pwsh_tool_v2.py: arguments.get("workspace") or arguments.get("workdir") or os.getcwd()). So a caller's directory is discarded twice over, and the command runs in the session's working directory while the description has told the caller it would run somewhere else.

That the injection is unconditional is deliberate and has to stay: with and "workdir" not in args a model could name the root it was trusted in — measured, workdir=<home> plus a write to .zshrc was allowed, because the home directory became "the workspace" (that method's docstring, design §2.4). The defect is not the injection. It is the sentence.

Observed

On this host, in a session whose working directory was the project checkout: a shell call carrying workdir=<session>/tmp — a directory that exists, Test-Path answers true — answered Get-Location = the session root. A second call naming a directory that holds a package.json answered Test-Path package.json = false, though that file is there. Every other argument of those calls arrived unchanged; only the directory did not.

What a fix must satisfy

  1. The two shell descriptions stop advising a parameter the daemon replaces, and say what to do instead: the command changes directory itself, because the run always starts in the session's working directory.
  2. The workdir field's own description states that the daemon supplies it and replaces any value given, so a reader who sees the field is not left thinking it is theirs to set.
  3. The guard that reads this sentence reads it against the injection, not against the schema. Today tests/test_shell_tool_descriptions.py::test_the_fresh_process_sentence_advises_a_parameter_that_exists only asserts that workdir is a declared property of the definition — an assertion that cannot fail while the parameter exists, whatever the daemon does with its value. Read against the behaviour, the same guard reddens if a description offers the directory again.
  4. Both dialects state it in the same words, which is what the rest of that file already requires of every shared sentence.
  5. glob and grep keep honouring the directory their caller names. Their workdir/path decides a preference rather than a boundary, and the two classes must not be conflated by a fix aimed at the shell tools.

Activity

  1. argszero commented on Nov 3, 2026

    @argszero
    Owner

    Handled by #2700

  2. pm25coder commented on Nov 3, 2026

    @pm25coder
    CollaboratorAuthor

    Handled by #2701

  3. pm25coder commented on Nov 3, 2026

    @pm25coder
    CollaboratorAuthor

    Correction to the pointer above: #2701 is closed and folded into #2700, which is the PR that finishes this issue — it was opened 14 minutes earlier, and one issue is finished by one PR. Nothing else about this issue changes; it stays open until #2700 lands.

  4. how2how2how2-arch commented on Nov 3, 2026

    @how2how2how2-arch
    Collaborator

    Landed as 84b84ef (#2700). Master's tree after the merge is 8abe16e90083124ad30c7fcfae0bf55375f2cf81, byte-identical to the landing tree the suite measured at 6111 passed / 37 skipped, so the reading that cleared the merge applies to master itself. Closed here because the pull request's closing keyword did not fire — the issue was opened after the pull request, so the reference predates it.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions