Skip to content

fix(agent-adapter): support fish when loading the project shell environment - #486

Open
adminliu-main wants to merge 1 commit into
masterfrom
qi/code-643
Open

fix(agent-adapter): support fish when loading the project shell environment#486
adminliu-main wants to merge 1 commit into
masterfrom
qi/code-643

Conversation

@adminliu-main

@adminliu-main adminliu-main commented Aug 27, 2026

Copy link
Copy Markdown

Linear: CODE-643

问题(Problem)

macOS 上用 fish 做登录 shell 时,启动 Codex 会话失败,UI 只显示:

操作失败 — Error: Agent failed to start

daemon 日志(~/Library/Logs/LinkCode/main.log)里有真实 cause:

Failed to load the project shell environment for /Users/adminliu
cause: Command failed: /opt/homebrew/bin/fish -i -l -c if command -v direnv …; fi
fish: Missing end to balance this if statement

根因(Root cause)

CODE-473 的 agent 子进程环境解析(resolveShellEnvironmentpackages/host/agent-adapter/src/shell-env.ts)会通过用户的登录 shell$SHELL)执行一段写死的 POSIX 命令来加载项目环境,并按需套一层 direnv exec

if command -v direnv >/dev/null 2>&1; then; else; fi

fish 不认 if/then/else/fi(它要求 if …; …; else; …; end),命令在解析阶段就挂了。该解析按设计大声失败、不回退(不回退到 launchd 的 PATH),于是 adapter.start() 抛错,被 orchestrator 包装成笼统的 Agent failed to start

  • 这段逻辑 Codex 和 Claude 共用,两个 agent 都受影响;
  • zsh / bash 等 POSIX 系 shell 不受影响,bug 仅 fish(macOS 上唯一语法不兼容 POSIX 的主流登录 shell);
  • 该探测仅 macOS 启用(shell-env.ts 的 darwin guard);Windows / Linux 直接复用 daemon 自身环境,不 spawn shell,PowerShell 不在此路径;
  • daemon 日志本身带完整 cause(fish 报错 + stack),只是 UI 文案笼统。

修复(Fix)

把命令构造抽成 shellProbeCommand(shell, printEnv)

  • fish → fish 语法(if …; …; else; …; end);
  • 其余 shell → 原 POSIX 命令,行为不变。

大声失败契约保留,未引入静默回退。

- const command = `if command -v direnv >/dev/null 2>&1; then exec direnv exec "$PWD" ${printEnv}; else exec ${printEnv}; fi`;
+ const command = shellProbeCommand(shell, printEnv);

+2 文件,+24/-2。

验证(Verification)

  • tsc --build --noEmit packages/host/agent-adapter 干净;
  • vitest run shell-env.test.ts — 3/3(新增 fish / POSIX 语法断言);
  • 改动文件 ESLint + Biome 干净;
  • 本机(登录 shell = fish)实测:新命令两个分支在真实 fish -i -l -c 下都能执行(exec / "$PWD" / direnv exec 均正常),并完整模拟 daemon 的调用方式成功解析出环境(PATH / HOME);zsh 实测走 POSIX 分支通过;
  • 真实 Codex 会话在 fish 下启动正常(本机实测通过);
  • pnpm check:ci 干净(0 error);
  • pnpm test 有 4 个既存失败,在 packages/foundation/common/src/node/__tests__/release-artifact.test.tsartifact path escapes its isolated root,macOS /tmp/private/tmp 符号链接问题),在 clean master 上同样复现,与本次改动无关(fix(agent-adapter,engine): stream codex rollout reads and evict expired history caches #466 也有同样说明)。

Checklist

  • pnpm check:cipnpm test 均通过(4 个 release-artifact 既存失败与本次无关,见验证)
  • 跑过受影响面并观察到改动生效
  • 若改了 wire 消息:WIRE_PROTOCOL_VERSION 已 bump(不适用)
  • 新代码与资源为本人原创,或来源与许可证兼容性已在上方说明
  • 行为变更处文档与注释已更新

@linear-code

linear-code Bot commented Aug 27, 2026

Copy link
Copy Markdown

CODE-643

@greptile-apps

greptile-apps Bot commented Aug 27, 2026

Copy link
Copy Markdown

Greptile Summary

The PR updates macOS project-environment probing to emit fish-compatible conditional syntax while preserving the existing POSIX command for other login shells.

  • Adds exact fish-shell detection and centralizes probe-command construction.
  • Adds unit assertions for fish, zsh, and bash command generation.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/host/agent-adapter/src/shell-env.ts Selects fish-compatible probe syntax for fish login shells while leaving the existing POSIX behavior unchanged elsewhere.
packages/host/agent-adapter/src/tests/shell-env.test.ts Adds focused command-generation coverage for fish and representative POSIX shells.

Reviews (4): Last reviewed commit: "fix(agent-adapter): support fish when lo..." | Re-trigger Greptile

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ℹ️ No blocking issues — the fix is correct, and I verified both emitted branches against a real fish. Two minor notes.

Reviewed changes — the single commit 1d7032d9, which makes the macOS login-shell environment probe emit fish-compatible syntax.

  • shellProbeCommand(shell, printEnv) extracted and exported (shell-env.ts:60-65), replacing the POSIX string previously inlined at the execFile call site. fish is detected with FISH_SHELL_PATTERN = /(?:^|\/)fish$/ against the $SHELL path.
  • fish gets if …; …; else; …; end; every other shell keeps a byte-identical copy of the string it replaces, so zsh/bash/sh behavior is unchanged and the loud-failure contract is preserved.
  • Golden-string test added (shell-env.test.ts:26-36) pinning both emitted strings.

ℹ️ The fish string is pinned by a string-equality test, so nothing executable guards it

The new test proves the fish branch is selected, but it compares a string to a string — it cannot prove the string is valid fish, and no CI job runs fish. I closed most of that gap by hand (evidence below), so this is not a request to add fish to CI; it is a note that the one remaining checklist item in the PR description is the part I could not reach, and that a future edit to either string is unguarded.

Technical details
# Verification evidence for the fish probe command

## What I confirmed independently

- **Both branches execute under real fish 3.7.0.** With a stub `direnv` on `PATH`, `command -v direnv` returns 0 and the `exec direnv exec "$PWD" …` branch runs; without it, the `else` branch runs. In both cases the marker-delimited JSON reaches stdout through `fish -i -l -c`.
- **`command -v` is still valid in current fish.** `src/builtins/command.rs` in fish `4.0.0` and in the latest tag `4.8.1` both carry `shortopts = "hasqv"` with `'v' => opts.find_path = true` and the comment `// -s and -v are aliases`. So the flag is not a 3.x-only affordance.
- **`fish -i` emits no stdout greeting under `-c`** — the greeting is driven from the interactive reader loop, which a batch `-c` invocation never enters. Observed output began directly at the marker. `parseShellEnvironment` would tolerate it regardless, since it slices between two occurrences of a random 32-hex marker.
- **`"$PWD"` is safe despite the daemon passing its own stale `PWD` in the child env.** `resolveShellEnvironment` spreads `baseEnv` (which carries the daemon's `PWD`) while setting a different `cwd`. fish guards this at `env/environment.rs` — it honors an inherited `PWD` only when `paths_are_same_file(incoming_pwd, ".")`, else falls back to `getcwd()`. Empirically fish, bash and dash all reported the `execFile` `cwd`, not the injected value. No fish-specific `direnv exec` misdirection.
- **`quoteShellArg`'s POSIX `'\''` escaping is fish-compatible** (fish treats `\'` outside quotes as a literal quote), and in practice never fires — the probe expression contains only double quotes.
- **No sibling site was missed.** `shell-env.ts:22` is the only place `$SHELL` receives a `-c` payload. `apps/daemon/src/pty/sidecar.ts:304-306` reads `$SHELL` but passes only `['-l']` with no synthesized command; `packages/host/engine/src/automation/shell-exec.ts:37-41` (`shell: true`) is Node's `/bin/sh`; `packages/host/engine/src/scripts/script-service.ts:189` hard-codes `/bin/sh`.

## What remains open

- A real Codex or Claude session started under fish *with the user's own `config.fish`* — the unchecked box in the PR description. My runs used a stock fish, so a user config that changes `$PWD`, shadows `direnv`, or exits early on a non-tty is still untested.

## Open questions for the human

- Is a shell-dialect matrix worth encoding anywhere, or is "fish only, everything else fails loudly" the intended long-term shape? The answer determines whether the next non-POSIX shell report is a one-line addition or a redesign.

ℹ️ Nitpicks

  • packages/host/agent-adapter/AGENTS.md:20-24 is the canonical prose for this path (the CODE-473 paragraph) and still describes the probe as simply "the user's login shell". One clause noting that the probe command is shell-dialect-specific — fish handled, other non-POSIX shells failing loudly — would save the next agent the trace into shell-env.ts.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread packages/host/agent-adapter/src/shell-env.ts

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ℹ️ No new issues in the code itself — the force-push left the tree byte-identical to what I already reviewed. One reverted doc line inline.

Reviewed changes — the force-push from 2d2fbf5f to f71574b3, which rewrote the branch into a single commit.

  • Collapsed the branch into one commit. f71574b3 carries the same parent as 1d7032d9 (2d66882c), so this was an amend-and-force-push rather than a rebase onto newer master.
  • Reverted the shellProbeCommand doc line. git diff-tree -r -p 1d7032d9 f71574b3 is empty — the tree matches the originally reviewed commit exactly, which means the only net change against the previously reviewed head is the loss of 2d2fbf5f's +1/-1.
  • Left the fix itself untouched. shellProbeCommand, FISH_SHELL_PATTERN and the golden-string test are byte-for-byte what I verified against a real fish last run, so none of that is re-litigated here.

ℹ️ A resolved review thread no longer matches the code, and GitHub can't be told

The shell-env.ts:59 thread from the previous review is still marked Resolved on GitHub, because clicking Commit suggestion resolved it — but the commit that resolved it is no longer on the branch. There is no un-resolve affordance, so that thread will keep asserting a fix that isn't in the tree. Worth knowing for the next reader more than for this PR: a resolved pullfrog thread is not evidence the code changed.

Technical details
# Resolved-thread state diverged from the tree after the force-push

## Affected sites
- `packages/host/agent-adapter/src/shell-env.ts:59` — holds the pre-suggestion wording again.
- Review thread `PRRT_kwDOS8Iqx86c4KKC` (comment id `3873279444`) — marked `RESOLVED`, asserting a change that is absent from `f71574b3`.

## Required outcome
- The doc line and the resolved thread agree with each other. Either re-apply the wording, or say the wording was rejected on the merits so the disagreement is on the record rather than looking like an accident.

## Open questions for the human
- Still unanswered from the initial review, and it is what this comment's wording actually communicates: is a shell-dialect matrix worth encoding somewhere, or is "fish only, everything else fails loudly" the intended long-term shape? That answer decides whether the next non-POSIX shell report is a one-line addition or a redesign.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread packages/host/agent-adapter/src/shell-env.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant