Skip to content

hooks/builtins: name the resolved shell in the env block - #4072

Open
trungutt wants to merge 3 commits into
docker:mainfrom
trungutt:feat/env-info-shell-and-syntax-hints
Open

hooks/builtins: name the resolved shell in the env block#4072
trungutt wants to merge 3 commits into
docker:mainfrom
trungutt:feat/env-info-shell-and-syntax-hints

Conversation

@trungutt

@trungutt trungutt commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

The problem

On Windows, docker-agent sessions repeatedly emit POSIX syntax — &&, grep, head, 2>/dev/null, ls -la, $(pwd) — under Windows PowerShell 5.1 or cmd.exe. Each attempt fails with a distinctive error, the model re-derives the dialect from scratch, and often emits the same wrong syntax on the next turn. Long sessions burn many tool calls this way.

Why

The env block at the top of every session says:

<env>
  Operating System: Windows
  ...
</env>

Windows alone doesn't disambiguate the shell (PowerShell 5.1? 7? cmd.exe? — different dialects). The model defaults to POSIX by habit since that's most of its training data. The shell tool description does carry a shell-specific hint (shellSyntaxHint), but it sits far from where the model is paying attention.

The change

Add a Shell: line to the env block, resolved via a new shellpath.ShellBaseName helper:

<env>
  Operating System: Windows
  ...
  Shell: powershell (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe)
</env>

Same treatment for pwsh, cmd, zsh, bash, etc. Prompt-cached, so no per-turn cost.

@trungutt
trungutt force-pushed the feat/env-info-shell-and-syntax-hints branch 2 times, most recently from 218d7eb to 4166ee0 Compare August 28, 2026 13:56
@aheritier aheritier added area/tools For features/issues/fixes related to the usage of built-in and MCP tools status/needs-triage For issues that need to be triaged labels Aug 28, 2026
@trungutt
trungutt force-pushed the feat/env-info-shell-and-syntax-hints branch 2 times, most recently from c22e7f5 to 75969f7 Compare August 28, 2026 14:17
The session_start env block previously named the OS but not the shell.
When the model reads 'Operating System: Windows' it defaults to POSIX
syntax by habit and emits '&&', 'grep', 'head', '2>/dev/null', 'ls -la',
'$(pwd)' — none of which work under Windows PowerShell 5.1 or cmd.exe.
The shell tool description already carries a shell-specific hint, but it
sits far from the user turn and is drowned out by the agent's own system
prompt; the env block sits at the top of every turn and is cached, so
that is where the disambiguation belongs.

Resolve the shell via the shared shellpath.DetectShell helper and print
its base name plus the resolved path. Long-form dialect rules live in
the shell tool description (shellSyntaxHint) and in the reactive
self-correction hint (shellDialectHint, follow-up commit) — this block
stays terse so it doesn't diverge from those and so the guidance stays
correct as models improve.

The base-name derivation moves to a new shellpath.ShellBaseName helper
so the same logic doesn't get copied into hooks/builtins; the
pre-existing copy in the shell toolset moves over here too.

No behaviour change on darwin/linux where the shell defaults to $SHELL
or /bin/sh; the extra 'Shell:' line lands cleanly in the existing
tab-indented env block.
@trungutt
trungutt force-pushed the feat/env-info-shell-and-syntax-hints branch 2 times, most recently from a6a2f90 to 465e895 Compare August 28, 2026 14:27
@trungutt trungutt changed the title hooks, shell: put the shell dialect in the env block and self-correct known syntax errors hooks/builtins: name the resolved shell in the env block Aug 28, 2026
@trungutt
trungutt marked this pull request as ready for review August 28, 2026 14:30
@trungutt
trungutt requested a review from a team as a code owner August 28, 2026 14:30
@aheritier aheritier added the kind/chore Maintenance, deps, CI, tooling (maps to chore: commit prefix) label Aug 28, 2026

@Sayt-0 Sayt-0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Shell: line in the env block addresses a real gap: sessions on Windows repeatedly emit POSIX syntax under PowerShell 5.1 / cmd.exe, and the existing shellSyntaxHint sits too far from the model's attention. The approach (reuse shellpath.DetectShell, prompt-cached line) is sound. Build and tests pass on the branch.

Two items to resolve before merge:

# Issue Files Severity
1 ShellBaseName duplicates an existing private helper pkg/shellpath/shellpath.go, pkg/tools/builtin/shell/shell.go blocking
2 Doc comment references shellDialectHint, which does not exist in the repo pkg/hooks/builtins/add_environment_info.go blocking

Details in the inline comments. Two minor nits included as well, take or leave.

Comment thread pkg/shellpath/shellpath.go
Comment thread pkg/hooks/builtins/add_environment_info.go Outdated
Comment thread pkg/hooks/builtins/add_environment_info.go Outdated
Comment thread pkg/hooks/builtins/add_environment_info.go
Drop the private helper and its table test that duplicated
pkg/shellpath.ShellBaseName. Prevents the two copies from drifting.
- Drop the stale reference to shellDialectHint, which does not exist.
- Restore the note that addEnvironmentInfo no-ops when Cwd is empty.
- Clarify that the discarded value from shellpath.DetectShell is
  argsPrefix, not an error.
@trungutt
trungutt requested review from a team and Sayt-0 August 28, 2026 17:10

@aheritier aheritier left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI is green on 3609910 (build-and-test, lint, license-check, windows-tests, build-image linux/amd64+arm64, CodeQL go/js/actions), and all three commits are signed and verified. Verified independently on the head commit: go build ./..., GOOS=windows GOARCH=amd64 go build ./pkg/..., and go test ./pkg/shellpath/... ./pkg/hooks/builtins/... ./pkg/tools/builtin/shell/... all pass.

The change does what the description says, and the previous round is fully addressed:

  • shellpath.ShellBaseName is now the single implementation: grep -rn '\bshellBaseName\b' returns nothing, both call sites (shell.go:371, shell.go:412) delegate, and the moved table test in pkg/shellpath/shellpath_test.go:10 is a superset of the removed one (adds /bin/sh and "").
  • grep -rn shellDialectHint returns nothing — the dead pointer is gone.
  • The "No-op when Cwd is empty." note and the argsPrefix clarification are back.
  • The core invariant holds: the hook resolves via shellpath.DetectShell(), the same source as shellHandler.shell (shell.go:294), so the env block cannot name a shell different from the one the tool executes.
  • The prompt-cache claim checks out — AddEnvironmentInfo output sits behind the cache_control marker (pkg/session/session.go:2227).

Findings

[should-fix] Docs enumerate the env block without the new shell field.
Two reference tables list the injected fields exhaustively and are now incomplete:

  • docs/configuration/hooks/index.md:209 — "Adds the working directory, git-repo status, OS, and CPU architecture."
  • docs/configuration/agents/index.md:96 — "injects working directory, OS, CPU architecture, and git info into context."

Adding the resolved shell to both keeps them accurate in the same change.

[should-fix] 465e8955's commit body will land in main with stale references.
Squash merge is disabled on this repo (allow_squash_merge: false), so all three commit messages are permanent history. That body cites shellDialectHint — which does not exist in the repo — and "follow-up commit", which stops resolving once the message is in history; it also states "the pre-existing copy in the shell toolset moves over here too", which only became true in 715df7ba. The code comment was corrected in 3609910, but the commit body still carries the dead pointer. Worth a reword on the next rebase.

[optional] The env-block / shell-tool agreement isn't tested.
add_environment_info_test.go:54,61 builds the expected string from the same shellpath.DetectShell() and shellpath.ShellBaseName() calls production uses, so it pins the line's placement and format but cannot fail if the hook's resolution later diverges (e.g. someone swaps in DetectUnixShell) — which is the invariant this change exists to protect. It matches the existing displayOS()/displayArch() style in that test, so take or leave.

[optional] pkg/shellpath's package comment (shellpath.go:1) still scopes the package to "safe shell binary resolution to prevent PATH hijacking attacks (CWE-426)", while ShellBaseName is a pure formatting helper. A one-line broadening keeps the doc honest now that the package hosts both.

Leaving this as a comment rather than an approval so the two [should-fix] items — the docs enumerations and the 465e8955 commit body — can be picked up first; neither is a correctness problem, and the code itself looks good to me. Note that merge is separately gated by the earlier CHANGES_REQUESTED review, which resolving threads does not dismiss; the original reviewer needs to re-review or that review needs dismissing.

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

Labels

area/tools For features/issues/fixes related to the usage of built-in and MCP tools kind/chore Maintenance, deps, CI, tooling (maps to chore: commit prefix) status/needs-triage For issues that need to be triaged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants