thời gian đọc: 13 minutes

Bạn khởi chạy OpenCode trong terminal của mình, mọi thứ đều hoạt động. Agent cố gắng thực hiện một`gh`— không tìm thấy. Một`java -version`— vắng mặt. Một`node`— không nơi nào cả. Tuy nhiên, những công cụ này thực sự hiện diện trong shell của bạn. Vấn đề là gì? OpenCode khởi chạy các shellkhông tương tácnhững người không bao giờ đọc`.zshrc`. Dưới đây là cách chẩn đoán và khắc phục điều này một cách chuẩn xác.

Bối cảnh: một agent mù trong một thế giới của các công cụ

Đó là một tối thứ Ba. Tôi vừa mới cài đặthttps://opencode.ai[OpenCode], tác nhân AI hứa hẹn sẽ thay đổi cách tôi lập trình. Bài kiểm tra đầu tiên: yêu cầu nó liệt kê các kho lưu trữ GitHub của tôi.

$ opencode
> Utilise gh pour lister mes repos
❌ bash: gh: command not found

Kỳ lạ.`gh`đã hoạt động hoàn hảo trong terminal của tôi. Tôi đang thử một cách khác:

> Vérifie la version de Java
❌ bash: java: command not found

Sau đó:

> Lance le build Gradle
❌ bash: gradle: command not found

Java, Gradle, Node,gh— mọi thứ đều vô hình đối với đặc vụ. Còn terminal của tôi thì nhìn thấy tất cả. Như thể đặc vụ và tôi đang sống trong hai thế giới song song.

Tôi đã mất hàng giờ để hiểu. Hàng giờ đồng hồ`echo $PATH`, de which java, với sự thất vọng ngày càng tăng. Cuối cùng tôi hiểu ra rằng vấn đề không nằm ở các công cụ — mà là ở cáishell. OpenCode, giống như mọi agent khởi chạy các tiến trình con, hoạt động trong các shellskhông tương tác. Và Zsh, trong các shell đó, đơn giản là bỏ qua your.zshrc`Vui lòng cung cấp văn bản tiếng Pháp cần dịch.

Sau đây là câu chuyện chi tiết về quá trình chẩn đoán, cách giải quyết và bài học mà tôi đã rút ra. Nếu bạn đang sử dụng một agent AI — OpenCode, Aider, Cursor, hoặc thậm chí là các scripts cron — vấn đề này sớm muộn gì cũng sẽ xảy ra với bạn.

Giải phẫu bug: hai shell, hai thế giới

Khi bạn mở một terminal, Zsh xử lý nó như một shelltương tác. Nó tải`.zshrc`, nơi khởi tạo tất cả mọi thứ: SDKMAN, NVM, pnpm, các alias, và prompt xinh xắn của bạn. Môi trường của bạn đã hoàn tất.

Nhưng khi OpenCode thực thi một lệnh, nó không khởi chạy một terminal tương tác. Nó khởi chạy một shellkhông tương tác— một shell tác vụ, không có con người phía sau màn hình. Và Zsh, trong ngữ cảnh này, bỏ qua`.zshrc`. Anh ấy chỉ đọc`.zshenv`Vui lòng cung cấp nội dung cần dịch.

Tại sao sự phân biệt này lại tồn tại? Bởi vì một shell không tương tác được thiết kế để thực thi các script, chứ không phải để phục vụ người dùng là con người. Việc tải các alias, prompt và completion vào một script chạy ngầm sẽ là một sự lãng phí. Vấn đề là PATH của bạn — thông tin quan trọng nhất để tìm các tệp thực thi — thường được khởi tạo trong`.zshrc`, không nằm trong`.zshenv`Vui lòng cung cấp văn bản cần dịch.

@startuml
skinparam backgroundColor #FEFEFE

actor "Bạn" as user
actor "OpenCode\n(agent)" as agent
participant "Shell\ntương tác\n(.zshrc được sourcé)" as ishell
participant "Shell\nkhông tương tác\n(.zshrc bị bỏ qua)" as nshell

user -> ishell : gh, java, node ✅
agent -> nshell : gh, java, node ❌

note right of ishell
File .zshrc nguồn:
- SDKMAN init
- NVM init
- ~/apps trong PATH
- pnpm trong PATH
end note

note right of nshell
.zshrc bị bỏ qua !
PATH = /usr/bin:/bin
+ một vài thư mục mặc định
end note

@enduml

Gốc rễ của vấn đề:Zsh chỉ source .zshrc cho các shell tương tác. OpenCode, giống như mọi tác nhân AI khởi chạy các tiến trình con, sử dụng các shell không tương tác. Những shell này chỉ đọc`.zshenv`Vui lòng cung cấp văn bản tiếng Pháp cần dịch.

Thủ phạm: .zshrc đối với `.zshenv

Zsh cóbốn tệp khởi tạo, mỗi người với một vai trò cụ thể. Đó là một thiết kế thanh thoát — nhưng đó cũng chính là mấu chốt của vấn đề:

Tệp Nguồn khi nào Vai trò Thay đổi PATH?

.zshenv

Luôn luôn(tương tác + không tương tác + đăng nhập)

Các biến môi trường thiết yếu, PATH

✅ Có — đó là chỗ của ANH ẤY/CÔ ẤY

.zprofile

Chỉ dành cho login shells

Các lệnh chậm (một lần mỗi phiên)

Có thể

.zshrc

Chỉ dành cho shell tương tác

Bí danh, prompt, hoàn tất, công cụ tương tác

❌ Không dành cho các biến quan trọng

.zlogin

Shell đăng nhập (sau .zshrc)

Tin nhắn chào mừng, hoàn tất

Hiếm khi

Bảng này đưa ra một gợi ý, nhưng cần phải hiểu nó một cách sâu sắc. Hãy coi nó như những căn phòng trong một ngôi nhà:

  • `.zshenv`làsảnh chờ— tất cả mọi người đều đi qua đây, dù là khách hay cư dân. Nếu bạn đặt thứ gì đó ở đây, mọi shell đều có thể nhìn thấy.

  • `.zshrc`là cáiphòng khách— chỉ những cư dân (shell tương tác) mới được vào trong. Những khách ghé thăm (shell không tương tác) sẽ ở lại sảnh.

  • .zprofile et `.zlogin`là các thành phần chuyên dụng cho các login shell (ví dụ như khi bạn kết nối qua SSH).

Bi kịch ở chỗ là hầu hết chúng ta đều đặt PATH ở trong phòng khách. Còn tác nhân AI thì lại không bao giờ được phép bước vào đó.

Vấn đề thông qua một sơ đồ:

@startuml
skinparam backgroundColor #FEFEFE

start

if (Shell interactif ?) then (Oui)
  :Source .zshenv;
  :Source .zprofile;
  :Source .zshrc;
  :Source .zlogin;
  note right
    SDKMAN ✅
    NVM ✅
    ~/apps ✅
    pnpm ✅
  end note
else (Non — shell non-interactif)
  :Source .zshenv uniquement;
  note right
    SDKMAN ❌
    NVM ❌
    ~/apps ❌
    pnpm ❌
  end note
endif

stop

@enduml

Khi bạn mở một terminal, Zsh ở chế độ tương tác: nó đọc`.zshrc`, mọi thứ đều hoạt động. Khi OpenCode khởi chạy một shell để thực thi một lệnh, Zsh ở chế độ không tương tác: nó bỏ qua`.zshrc`, chỉ đọc`.zshenv`. Et si `.zshenv`không tồn tại hoặc không chứa PATH — đó là một vùng hoang mạc.

Tại sao Zsh lại làm vậy?Đây là một lựa chọn thiết kế kế thừa từ Unix. Một shell không tương tác phải lànhanh et có thể tái lập. Việc tải các alias, các prompt có màu và các khởi tạo nặng của SDKMAN trong một script cron hoặc một agent AI sẽ chậm và dễ lỗi. Do đó, việc phân chia là hợp lý:`.zshenv`cho những phần thiết yếu (biến, PATH),`.zshrc`cho sự tiện lợi (aliases, prompt, complétons). Vấn đề xảy ra khi chúng ta đưa những thứ thiết yếu vào phần tiện lợi.

Chẩn đoán từng bước

Trước khi sửa, cần phải hiểu chính xác điều gì còn thiếu. Đây là một phương pháp chẩn đoán có thể tái lập — hãy lưu nó vào mục yêu thích nếu bạn làm việc với các tác nhân AI.

1. Kiểm tra PATH của agent

Chúng ta so sánh những gì terminal tương tác của bạn nhìn thấy với những gì một shell không tương tác nhìn thấy — tức là những gì OpenCode nhìn thấy.

# Dans votre terminal interactif (tout fonctionne)
echo $PATH | tr ':' '\n' | grep -v '^/usr' | sort

Kết quả điển hình:

/home/cheroliv/apps
/home/cheroliv/.nvm/versions/node/v22.19.0/bin
/home/cheroliv/.sdkman/candidates/java/current/bin
/home/cheroliv/.sdkman/candidates/gradle/current/bin
/home/cheroliv/.local/share/pnpm
/home/cheroliv/.local/bin

Bây giờ, hãy mô phỏng những gì agent nhìn thấy — một shell không tương tác:

# Shell non-interactif : pas de .zshrc
zsh -c 'echo $PATH' | tr ':' '\n' | grep -v '^/usr' | sort

Kết quả:

/home/cheroliv/.local/bin

Năm trên sáu con đường đã biến mất.Agent bị tước đi 83% môi trường của mình. Việc này giống như thể bạn được yêu cầu nấu ăn mà thiếu đi một nửa dụng cụ — bạn có thể đun sôi nước, nhưng không thể làm gì nhiều hơn thế.

Lệnh`zsh -c 'echo $PATH'`là của bạncông cụ chẩn đoán số 1. Nếu một công cụ mà bạn sử dụng hàng ngày không có trong kết quả, tác nhân AI của bạn cũng sẽ không thể nhìn thấy nó. Hãy kiểm tra nó trước và sau mỗi lần sửa đổi của`.zshenv`Vui lòng cung cấp nội dung cần dịch.

2. Xác định những gì có trong .zshrc nhưng không có trong `.zshenv

Bây giờ, chúng ta tìm kẻ thủ ác trong`.zshrc`. Chúng ta lọc các dòng có đề cập đến các công cụ của mình:

grep -n 'apps\|SDKMAN\|NVM\|PNPM\|PATH' ~/.zshrc

Điển hình, ta tìm thấy:

119: PATH="/usr/bin/python3:$HOME/apps:$PATH"           # ← pas exporté !
171: export NVM_DIR="$HOME/.nvm"
172: [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"   # ← NVM init
176: nvm use --lts --silent                              # ← active une version
179: export PNPM_HOME="/home/cheroliv/.local/share/pnpm"
187: export SDKMAN_DIR="$HOME/.sdkman"
188: [[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"

Tất cả những điều đó làvô hìnhcho OpenCode. Và cái`PATH`của dòng 119 thậm chí còn không`export`é — nó không bao giờ rời khỏi shell hiện tại. Đây là một chi tiết kỹ thuật quan trọng: một biến không có`export`vẫn mang tính cục bộ trong shell định nghĩa nó. Các shell con — như những shell được chạy bởi OpenCode — không bao giờ kế thừa nó.

3. Kiểm tra xem .zshenv có tồn tại không

cat ~/.zshenv 2>/dev/null || echo "FICHIER ABSENT"

Nếu câu trả lời là « FICHIER ABSENT », thì đó chính là lúc mọi thứ được quyết định.

Giải pháp: .zshenv + các đường dẫn chính xác

Chiến lược này đơn giản, nhưng đòi hỏi sự chính xác: đặt vào trong`.zshenv` duy nhấtcác đường dẫn thiết yếu, mà không nguồn hóa các script khởi tạo nặng nề. Chúng ta không di chuyển`.zshrc`trong`.zshenv`— chúng ta trích xuất những nội dung chính.

Tạo .zshenv với tất cả các đường dẫn thiết yếu

`.zshenv`là cáichỉ một tệpmà Zsh đảm bảo sẽ source trongtất cảcác ngữ cảnh. Đó là nơi PATH cần được đặt vào.

export SDKMAN_DIR="$HOME/.sdkman"
export NVM_DIR="$HOME/.nvm"
export PNPM_HOME="$HOME/.local/share/pnpm"
export PATH="$HOME/apps:$HOME/.sdkman/candidates/java/current/bin:$HOME/.sdkman/candidates/gradle/current/bin:$HOME/.nvm/current/bin:$PNPM_HOME:$HOME/.local/bin:$HOME/.local/share/JetBrains/Toolbox/scripts:/usr/bin/python3:$PATH"

Chúng ta sử dụng`$HOME/.nvm/current/bin`và không`$HOME/.nvm/versions/node/v22.19.0/bin`. Lý do là: các phiên bản Node thay đổi. Một đường dẫn cố định sẽ trở nên sai ở lần tiếp theo`nvm install`. Chi tiết hơn trong phần tiếp theo.

Tại sao không dùng source sdkman-init.sh trong .zshenv?

Cách tiếp cận hấp dẫn nhất sẽ là chỉ đơn giản tái hiện trong`.zshenv`những gì chúng ta làm trong`.zshrc`— nạp các script khởi tạo. Chúng tacó thểcó thể bị cám dỗ để làm:

# ❌ MAUVAISE IDÉE
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"

Các vấn đề:

  • ChậmHãy cung cấp văn bản cần dịch.`sdkman-init.sh`thực hiện phân giải mạng và kiểm tra ở mỗi shell. Trong một shell không tương tác, đây là một chi phí không cần thiết.

  • Hàng dễ vỡ: SDKMAN mong đợi một ngữ cảnh tương tác. Quá trình khởi tạo của nó có thể thất bại trong im lặng trong một pipe hoặc một sub-shell.

  • Vô ích: SDKMAN đặt các ứng dụng ứng viên của mình trong`~/.sdkman/candidates/<tool>/current/bin`— các symlink ổn định trỏ đến phiên bản đang hoạt động. Chúng ta có thể sử dụng chúngtrực tiếpVui lòng cung cấp văn bản cần dịch.

Cách tiếp cận đúng: bỏ qua việc khởi tạo SDKMAN và trỏ trực tiếp đến các symlinks`current`. Đó là chìa khóa của toàn bộ giải pháp:sử dụng cấu trúc tệp làm hợp đồng, thay vì mã khởi tạoHãy cung cấp nội dung cần dịch.

@startuml
skinparam backgroundColor #FEFEFE

rectangle "Cách tiếp cận LENTE\n(source sdkman-init.sh)" as slow {
  card ".zshenv source sdkman-init.sh\n2-3 giây mỗi shell\nKiểm tra mạng\nNguy cơ thất bại" as s1 #FDEDEC
}

rectangle "Cách tiếp cận RAPIDE\n(symlinks directs)" as fast {
  card ".zshenv export PATH=...current/bin\n0 ms\nKhông có mạng\nKhông khởi tạo" as s2 #E8F8E8
}

slow --> fast : Kết quả cuối cùng\n PATH trỏ đến /bin__\ntrong hai trường hợp

@enduml

Trường hợp NVM: khi trình quản lý phiên bản quên để lại dấu vết

Vấn đề NVM

Đây là nơi cuộc điều tra đã dẫn tôi đi xa nhất. SDKMAN có một thiết kế thanh thoát: khi chúng ta thực hiện`sdk install java 25.0.2-tem`, nó tạo một symlink:

~/.sdkman/candidates/java/current -> ~/.sdkman/candidates/java/25.0.2-tem

Symlink này làluôn cập nhật. Di chuột lên đó trong`.zshenv`và bạn được bảo vệ, bất kể sử dụng shell nào. Thật tuyệt vời.

Còn NVM thì không tạokhông có gìđiện thoại. Không có symlink`current`. Không có điểm neo ổn định. Chúng ta phải trỏ đến một đường dẫn được đánh phiên bản cố định, trông như thế này:

~/.nvm/versions/node/v22.19.0/bin/node

Hẹn gặp lại lần tới`nvm install 24`, đường dẫn này đã chết. Của bạn`.zshenv`sẽ trỏ đến một phiên bản không còn là phiên bản hoạt động. Đó là một quả bom hẹn giờ.

Tại sao NVM lại làm như vậy?NVM hoạt động bằng cách thay đổi động PATH mỗi khi`nvm use`. Nó được thiết kế cho những nhà phát triển thường xuyên thay đổi phiên bản, trong một shell tương tác. Symlink`current`không nằm trong các thông số kỹ thuật ban đầu — đó là một thiếu sót trong thiết kế mà chúng tôi sẽ tự khắc phục.

@startuml
skinparam backgroundColor #FEFEFE

package "SDKMAN ✅" {
  [~/.sdkman/candidates/java/current] as sdk_current
  [~/.sdkman/candidates/java/25.0.2-tem/] as java_25
  [~/.sdkman/candidates/java/21.0.7-tem/] as java_21

  sdk_current --> java_25 : symlink
}

package "NVM ❌ (trước khi sửa)" as nvm_before {
  [~/.nvm/versions/node/v22.19.0/] as node_22
  [~/.nvm/versions/node/v20.16.0/] as node_20
  note right of node_22
Không có symlink current!
.zshenv phải trỏ cứng
Sẽ bị hỏng ở lần nvm install tiếp theo
  end note
}

package "NVM ✅ (sau khi sửa)" as nvm_after {
  [~/.nvm/current] as nvm_current
  [~/.nvm/versions/node/v22.19.0/] as node_22b
  [~/.nvm/versions/node/v20.16.0/] as node_20b

  nvm_current --> node_22b : symlink\n(mis à jour auto)
}

@enduml

Vì NVM không làm điều đó, nên chúng ta sẽ tự làm. Nguyên lý tương tự như SDKMAN — một symlink`current`luôn trỏ đến phiên bản đang hoạt động. Chúng ta tạo nó một lần, sau đó tự động hóa để nó tự cập nhật.

# Créer le symlink initial
ln -sfn "$HOME/.nvm/versions/node/v22.19.0" "$HOME/.nvm/current"

Hiện tại, trong`.zshenv`, chúng ta sử dụng:

$HOME/.nvm/current/bin

Thay vì:

# ❌ Chemin en dur — cassé au prochain changement de version
$HOME/.nvm/versions/node/v22.19.0/bin

Một symlink sẽ không có giá trị nếu nó không tự cập nhật. Symlink`current`phải được cập nhật khi chúng ta thực hiện`nvm use` ou nvm install. Giải pháp: mộtwrapper— một hàm bao bọc lệnh thực sự`nvm`và cập nhật symlink sau mỗi lần gọi.

# À la fin de .zshrc, APRÈS le chargement de NVM
nvm use --lts --silent
ln -sfn "$(nvm_version_path "$(nvm current)")" "$NVM_DIR/current"

_nvm() {
  command nvm "$@"
  local rc=$?
  ln -sfn "$(nvm_version_path "$(nvm current)")" "$NVM_DIR/current"
  return $rc
}
alias nvm='_nvm'

Cách thức hoạt động chi tiết:

@startuml
skinparam backgroundColor #FEFEFE

actor Développeur
participant "wrapper nvm\n(_nvm)" as wrapper
participant "nvm thực tế\n(lệnh nvm)" as realnvm
participant "~/.nvm/current\n(symlink)" as symlink

Développeur -> wrapper : nvm use 20
wrapper -> realnvm : command nvm use 20
realnvm --> wrapper : version activée
wrapper -> symlink : ln -sfn .../v20.16.0 ~/.nvm/current
wrapper --> Développeur : retour

note right of symlink
Luôn cập nhật!
Được sử dụng bởi `.zshenv`
→ có thể truy cập bởi OpenCode
end note

@enduml

Wrapper`_nvm`gọi lệnh thực tế`nvm`, sau đó cập nhật symlink. Bí danh`nvm='_nvm'`đảm bảo rằng khi nhập`nvm`, chúng ta đi qua wrapper. Và khi khởi tạo shell, chúng ta cũng làm tương tự sau đó`nvm use --lts`Vui lòng cung cấp văn bản cần dịch.

`nvm_version_path`là một hàm nội bộ của NVM dùng để phân giải đường dẫn đầy đủ của một phiên bản. Điều này giúp tránh việc phải xây dựng lại đường dẫn một cách thủ công.

Bảng tổng kết các đường dẫn

Trước khi chuyển sang các cạm bẫy, một bản tóm tắt trực quan về sự thay đổi. Ở bên trái, những gì bạn đã có (tất cả trong`.zshrc`, vô hình đối với tác nhân). Ở bên phải, những gì bạn đang có hiện nay (các đường dẫn thiết yếu trong`.zshenv`, hiển thị ở mọi nơi).

Công cụ Trước (chỉ .zshrc) Sau (.zshenv + symlink) Có hiển thị bởi OpenCode không?

~/apps(gh, vscode)

PATH không được export trong .zshrc

`$HOME/apps`trong .zshenv

✅

SDKMAN Java

`sdkman-init.sh`được source trong `.zshrc

`$HOME/.sdkman/candidates/java/current/bin`trong .zshenv

✅

SDKMAN Gradle

`sdkman-init.sh`được nạp trong `.zshrc

`$HOME/.sdkman/candidates/gradle/current/bin`trong .zshenv

✅

NVM Node

`nvm use --lts`trong .zshrc

`$HOME/.nvm/current/bin`trong .zshenv (symlink)

Văn bản cần dịch không được cung cấp. Vui lòng gửi đoạn văn bản tiếng Pháp cần dịch.

pnpm

`$PNPM_HOME`trong `.zshrc

`$PNPM_HOME`trong .zshenv

✅

JetBrains Toolbox

Tự động thêm bởi Toolbox vào `.zshrc

`$HOME/.local/share/JetBrains/Toolbox/scripts`trong .zshenv

✅

Python 3

`/usr/bin/python3`trong PATH .zshrc

Một cách rõ ràng trong .zshenv

Vui lòng cung cấp văn bản tiếng Pháp cần dịch.

Các bẫy và biện pháp giảm thiểu

Không phải mọi thứ đều hoàn hảo với cách tiếp cận này. Dưới đây là những vấn đề tôi đã gặp phải và cách khắc phục chúng.

Bẫy Mô tả Giảm thiểu

PATH bị trùng lặp

Nếu .zshenv và .zshrc cùng thêm một đường dẫn, nó sẽ xuất hiện hai lần

`.zshenv`được lấy nguồntrước.zshrc. Cả hai đều được đọc trong một shell tương tác. PATH có thể bị trùng lặp. Điều này chỉ mang tính thẩm mỹ, không ảnh hưởng đến chức năng. Để tránh điều này: chỉ đặt vào .zshenv những đường dẫn không có trong PATH mặc định.

NVM : phiên bản cố định trong `.zshenv

Đặt`~/.nvm/versions/node/v22.19.0/bin`cứng trở nên sai ở lần tiếp theo`nvm install`

Sử dụng symlink`$HOME/.nvm/current/bin`+ wrapper`_nvm`trong `.zshrc

SDKMAN : source sdkman-init.sh trong `.zshenv

Chậm, mong manh, vô dụng trong một shell không tương tác

Sử dụng các symlinks`candidates/<tool>/current/bin`trực tiếp

Thiếu bí danh sau khi sửa đổi

Wrapper`nvm='_nvm'`trong .zshrc chỉ có hiệu lực sau khi tải lại

Khởi động lại shell hoặc`source ~/.zshrc`

OpenCode không nhìn thấy các thay đổi

Agent đã khởi chạy các sous-shell của mình với tệp .zshenv cũ

Khởi động lại OpenCode sau khi sửa đổi `.zshenv

.zshenv quá tải

Đặt các hàm nặng hoặc các khởi tạo tương tác vào `.zshenv

.zshenv = chỉ các biến môi trường + PATH. Không có`source`, không có các hàm nặng, không có các prompt.

Bài học kinh nghiệm

  1. .zshrc` mang tính tương tác, .zshenv mang tính phổ quát— Nếu một biến cần tồn tại trong tất cả các shell (tác nhân AI, cron, script, IDE), nó sẽ nằm trong`.zshenv`Phòng khách rất thoải mái, nhưng sảnh vào là nơi duy nhất mà mọi người đều đi qua.

  2. Các trình quản lý phiên bản không giống nhau— SDKMAN tạo các symlinks`current`theo thiết kế. NVM thì không. Bạn phải bù đắp thiếu sót này một cách thủ công. Đây là một bài học quan trọng: trước khi cấu hình PATH của bạn, hãy kiểm tra xem trình quản lý của bạn có cung cấp một điểm neo ổn định hay không.

  3. Không source các init scripts trong .zshenv—`sdkman-init.sh et `nvm.sh`được thiết kế cho shell tương tác. Chúng chậm và dễ lỗi trong ngữ cảnh không tương tác. Các symlinks`current`là đủ và tức thời.

  4. Luôn kiểm tra trong shell không tương tác—`zsh -c 'echo $PATH'`mô phỏng chính xác những gì một agent nhìn thấy. Đây là bài kiểm tra xác thực. Nếu không có bài kiểm tra này, bạn sẽ không biết liệu cấu hình của mình có hoạt động cho các agent hay không.

  5. Wrapper pattern có thể tái sử dụng— Cùng một pattern`_nvm`+`alias nvm='_nvm'`áp dụng cho bất kỳ công cụ nào thay đổi PATH một cách linh động mà không để lại dấu vết ổn định. Đây là một công cụ nữa trong hộp ý tưởng của bạn.

@startuml
skinparam backgroundColor #FEFEFE

rectangle "TRƯỚC" as avant {
  card "Shell tương tác: ✅\nShell không tương tác: ❌\nOpenCode: ❌\nCron: ❌\nScripts: ❌" as av1 #FDEDEC
}

rectangle "SAU" as apres {
  card "Shell tương tác: ✅\nShell không tương tác: ✅\nOpenCode: ✅\nCron: ✅\nScripts: ✅" as ap1 #E8F8E8
}

avant --> apres : .zshenv +\nsymlink ~/.nvm/current

@enduml

Kiểm tra cuối cùng

Sau khi tạo`.zshenv`và symlink NVM, hãy xác nhận rằng mọi thứ hoạt động bình thường — trong cả hai ngữ cảnh:

# Shell interactif (votre terminal)
gh --version && java -version && node --version
# Shell non-interactif (simulation OpenCode)
zsh -c 'gh --version && java -version && node --version'

Cả hai đều phải thành công. Nếu có, tác nhân AI của bạn sẽ thấy các công cụ giống như bạn. Nếu không, hãy quay lại bước chẩn đoán từng bước — có khả năng bạn đã quên một đường dẫn hoặc symlink NVM chưa được cập nhật.

Tự động hóa bài kiểm tra này.Hãy thêm bước kiểm tra này vào một script healthcheck mà bạn chạy sau mỗi lần cập nhật các công cụ của mình. Một`zsh -c 'which java && which node && which gh'`trong CI, đó là một sự đảm bảo chống lại những điều bất ngờ.

Vui lòng cung cấp văn bản cần dịch. Một shell không tương tác giống như một vị khách lặng lẽ: nó chỉ đọc những gì được hiển thị trên cánh cửa ra vào. Nếu PATH nằm ở trong phòng khách, nó sẽ không bao giờ nhìn thấy. Vui lòng cung cấp nội dung cần dịch.

Liên kết

Tài liệu chính thức

Các công cụ được đề cập

Để tìm hiểu thêm

Bài viết liên quan