SSD Nodes Learn 🎉 VPS từ $4.99/tháng
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-07

Cấu hình statusline Claude Code trên VPS

Tạo statusline Claude Code bằng script để hiện hostname, thư mục, git branch và model dưới prompt. Bạn tránh sửa nhầm server trong các phiên SSH.

Claude Code statusline hiển thị gì

Claude Code statusline là một dòng nằm bên dưới prompt và hiển thị đầu ra của một script do bạn viết. Bạn thêm block statusLine vào settings.json rồi trỏ block đó đến một command. Claude Code chạy command đó, gửi trạng thái session cho command dưới dạng JSON qua standard input, rồi in mọi nội dung command ghi vào standard output.

Đó là toàn bộ contract. Script của bạn đọc JSON từ stdin và in text ra stdout. Script chạy trên máy của bạn. Không có nội dung nào nó in ra được gửi đến model, nên không tốn token.

Trên một laptop chỉ có một project, statusline chỉ mang tính trang trí. Trên 3 server, nó là một lớp bảo vệ. Mọi session Claude Code đều có giao diện giống nhau trong mọi terminal. Vì vậy, 4 cửa sổ SSH không có nhãn rất dễ khiến bạn chạy migration nhầm máy. Một statusline bắt đầu bằng hostname sẽ loại bỏ nhóm lỗi này.

Vị trí của thiết lập statusLine trong settings.json

Đặt thiết lập này trong user settings tại ~/.claude/settings.json. Thiết lập này áp dụng cho mọi project trên máy đó. Project settings tại .claude/settings.json bên trong một repository cũng hoạt động và được ưu tiên trong thư mục đó.

{
  "statusLine": {
    "type": "command",
    "command": "~/.claude/statusline.sh"
  }
}

type luôn là "command". Giá trị command được chạy qua shell, nên có thể là đường dẫn đến script hoặc một command đơn giản. Hãy kiểm tra wiring hoạt động trước khi viết script:

{
  "statusLine": {
    "type": "command",
    "command": "hostname -s"
  }
}

Khởi động Claude Code và gửi một message. Thanh bên dưới prompt lúc này sẽ hiển thị short hostname của server. Nếu thanh vẫn trống, vấn đề nằm ở setting hoặc trust dialog, không phải script của bạn. Đọc phần "Vì sao statusline vẫn trống" bên dưới.

Tính đến August 2026, có thêm 3 key tùy chọn. padding thêm khoảng cách ngang theo số ký tự và mặc định là 0. refreshInterval chạy lại command sau mỗi N giây, ngoài các trigger thông thường, với giá trị tối thiểu là 1. Chỉ dùng tùy chọn này khi dòng hiển thị clock hoặc một giá trị thay đổi trong lúc session idle. hideVimModeIndicator ẩn phần text -- INSERT -- tích hợp sẵn khi script của bạn đã render vim mode.

Statusline script nhận dữ liệu gì?

Không được tin bất kỳ danh sách field nào bạn đọc ở đâu đó, kể cả trên trang này. Hãy lấy object thực tế mà phiên bản của bạn gửi. Viết một script dùng tạm để lưu stdin vào một file:

cat > ~/.claude/statusline-capture.sh <<'EOF'
#!/bin/bash
cat > /tmp/statusline-input.json
echo "captured"
EOF
chmod +x ~/.claude/statusline-capture.sh

Trỏ statusLine.command vào file đó, khởi động một session và gửi một message. Bar đọc captured. Bây giờ xem dữ liệu đã nhận:

jq . /tmp/statusline-input.json

Bạn đã có đúng cấu trúc cho bản build của mình và có thể lặp lại việc này bất cứ lúc nào bản update thay đổi schema.

Các phần ổn định, theo tài liệu vào tháng 8 năm 2026, là các object lồng nhau thay vì các key phẳng. model chứa iddisplay_name. workspace chứa current_dirproject_dir: current_dir là thư mục hiện tại của session, project_dir là thư mục nơi session được khởi chạy, và hai giá trị này khác nhau khi working directory thay đổi giữa session. cwd ở cấp cao nhất chứa cùng giá trị với workspace.current_dir. context_window chứa số lượng token cùng với used_percentage được tính sẵn. cost chứa total_cost_usd và các bộ đếm thời lượng. session_id ổn định trong suốt vòng đời của session và là giá trị duy nhất giữa các session, điều này quan trọng cho việc cache sau này.

Ba quy tắc giúp script hoạt động ổn định khi schema thay đổi.

Một số key bị thiếu, không phải null. vim, agent, pr, worktreeeffort chỉ xuất hiện khi feature tương ứng đang hoạt động. Đọc .vim.mode bằng jq -r khi vim mode đang tắt sẽ in ra chuỗi nguyên văn null, khiến bar hiển thị null cho người đọc. Hãy thêm // empty vào mọi selector để key bị thiếu không in ra gì.

Một số giá trị là null ở giai đoạn đầu. context_window.used_percentagecontext_window.current_usage là null trước khi có response đầu tiên từ API, còn current_usage trở lại null sau /compact cho đến khi lần gọi tiếp theo điền lại giá trị. Vì vậy, phần trăm context trên bar cần // 0; nếu không, nó sẽ đọc null trong vài giây đầu của mỗi session. Trước khi đưa con số đó lên bar, bạn nên biết context window thực sự được sử dụng như thế nào.

Git branch không nằm trong JSON. Không có field nào báo branch. Bất kỳ branch nào xuất hiện trên bar đều do script của bạn tự chạy git.

Statusline script có khả năng suy giảm thay vì bị lỗi

Đây là phiên bản có thể copy-paste. Script in ra hostname, thư mục làm việc, git branch và model name. Mỗi trường đều có giá trị dự phòng, nên ngay cả một JSON object rỗng vẫn tạo ra một dòng có thể sử dụng.

#!/bin/bash
# ~/.claude/statusline.sh
input=$(cat)

# Read one field. Prints nothing when the key is missing or null.
field() { printf '%s' "$input" | jq -r "$1 // empty" 2>/dev/null; }

HOST=$(hostname -s 2>/dev/null)
[ -z "$HOST" ] && HOST="host"

DIR=$(field '.workspace.current_dir')
[ -z "$DIR" ] && DIR=$(field '.cwd')
[ -z "$DIR" ] && DIR="$PWD"

MODEL=$(field '.model.display_name')
[ -z "$MODEL" ] && MODEL="claude"

SHORT="$DIR"
if [ -n "$HOME" ]; then
  case "$DIR" in
    "$HOME") SHORT="~" ;;
    "$HOME"/*) SHORT="~/${DIR#"$HOME"/}" ;;
  esac
fi

BRANCH=""
if git -C "$DIR" rev-parse --git-dir >/dev/null 2>&1; then
  BRANCH=$(git -C "$DIR" branch --show-current 2>/dev/null)
  [ -z "$BRANCH" ] && BRANCH="detached"
fi

CYAN=$'\033[36m'
YELLOW=$'\033[33m'
DIM=$'\033[2m'
RESET=$'\033[0m'

LINE="${CYAN}${HOST}${RESET} ${SHORT}"
[ -n "$BRANCH" ] && LINE="${LINE} ${YELLOW}${BRANCH}${RESET}"
LINE="${LINE} ${DIM}${MODEL}${RESET}"

printf '%s\n' "$LINE"

Mọi lần đọc đều đi qua field, lệnh này thêm // empty, nên key đã bị đổi tên hoặc xóa sẽ tạo ra một chuỗi rỗng và dòng tiếp theo sẽ cung cấp giá trị mặc định. Thư mục sẽ lần lượt dùng giá trị dự phòng từ workspace.current_dir sang cwd rồi sang $PWD. Branch được lấy từ git -C "$DIR" thay vì dùng git trực tiếp, nên branch luôn khớp với thư mục mà statusline đang hiển thị.

Lưu file, sau đó cấp quyền thực thi:

chmod +x ~/.claude/statusline.sh

Quyền thực thi là bắt buộc. Claude Code chạy command thông qua shell, nên script không có +x sẽ lỗi với Permission denied, không ghi gì ra stdout và dòng này vẫn trống mà không có lỗi hiển thị.

jq dùng để phân tích JSON trên command line và không được cài sẵn trên Ubuntu server mới:

sudo apt update && sudo apt install -y jq

Sau đó trỏ setting đến script này, dùng block settings.json đầu tiên ở trên.

Kiểm tra script trước khi tin dùng

Chạy script bằng tay 2 lần. Lần đầu với một session object bình thường:

echo '{"model":{"display_name":"Opus"},"workspace":{"current_dir":"/srv/api"},"session_id":"t1"}' | ~/.claude/statusline.sh

Bạn nhận được hostname, sau đó là /srv/api, rồi Opus. Không có nhánh nào xuất hiện, vì /srv/api trên máy của bạn có thể không phải là một git repository.

Tiếp theo là bài kiểm tra khi dữ liệu suy giảm. Đây là bước nhiều người bỏ qua:

echo '{}' | ~/.claude/statusline.sh

Một object rỗng là trường hợp xấu nhất mà thay đổi schema có thể trả về. Dòng lệnh vẫn in ra hostname, thư mục hiện tại từ $PWD, và từ claude ở vị trí tên model. Không có gì bị crash và cũng không có gì in ra null. Một script vượt qua bài kiểm tra này vẫn hoạt động khi một field bị đổi tên, vì đối với script, field bị đổi tên và field bị thiếu là cùng một sự kiện.

Những gì bạn sẽ thấy

statusline hiển thị trên một dòng riêng phía trên các badge footer tích hợp sẵn và không thay thế chúng. Trong cấu hình hoạt động đúng, dòng này gồm: hostname ngắn màu cyan, tiếp theo là thư mục hiện tại với thư mục home được rút gọn thành ~, rồi tên branch màu vàng khi thư mục đó là một git repository, cuối cùng là tên model ở dạng mờ. Kết quả sẽ gần giống web-01 ~/api main Opus, với bốn phần đó được tô màu.

Dòng này chạy lại script khi session bắt đầu, kể cả khi resume; khi có message mới từ assistant; sau khi /compact hoàn tất; khi permission mode thay đổi; khi vim mode được bật hoặc tắt; và theo chu kỳ refreshInterval nếu bạn đặt chu kỳ. Các lần cập nhật được debounce ở 300 ms, nên một loạt thay đổi liên tiếp chỉ chạy script một lần. Thanh này ẩn trong khi autocomplete, help menu và permission prompt hiển thị, sau đó xuất hiện lại.

Tên máy chủ phải đứng đầu

Khi bạn chạy agent trên nhiều server, terminal là thứ duy nhất cho biết bạn đang ở đâu, nhưng terminal có thể hiển thị sai. Mở thêm một kết nối ssh từ bên trong một pane tmux, tiêu đề cửa sổ thường vẫn giữ tên cũ vì tiêu đề được đặt bởi shell chưa biết rằng nó đã chuyển sang nơi khác. Để Claude Code chạy trong một tmux session tách rời trên VPS rồi attach lại sau một ngày, bạn sẽ không thấy gì trên màn hình giúp phân biệt build server với production server.

statusline khác ở chỗ Claude Code tự render nó cho từng session, dựa trên dữ liệu mà session đó lưu giữ. Nó không thể kế thừa từ pane khác hoặc giữ giá trị cũ do shell prompt chưa được refresh. Nội dung hiển thị chính là server nơi agent đang ghi file.

Đặt mỗi server một màu riêng để bạn nhận ra nó trước khi đọc. Thêm 2 dòng bên trên phần gán LINE=:

CODE=$(printf '%s' "$HOST" | cksum | cut -d' ' -f1)
HOST_COLOR=$(printf '\033[%dm' "$((31 + CODE % 6))")

Sau đó dùng ${HOST_COLOR} thay cho ${CYAN}. cksum in ra checksum của hostname, nên mỗi tên luôn ánh xạ đến cùng một màu trong khoảng 31 đến 36, tương ứng từ đỏ đến cyan. Copy cùng một script lên mọi server để mỗi server tự hiển thị nhãn của mình.

Thư mục cũng cần được hiển thị vì cùng một lý do. /srv/api/srv/api-staging chỉ cách nhau một lần nhấn phím trong lệnh ssh, nhưng hậu quả có thể cách nhau cả một sự cố. Model và branch là 2 thông tin còn lại đáng dùng diện tích: model cho biết bạn đã resume session nào, còn branch cho biết agent sắp commit vào main hay chưa.

Màn hình nhỏ càng làm các thông tin này quan trọng hơn vì không có tiêu đề cửa sổ để dựa vào. Nếu bạn dùng setup như vậy, hãy xem điều khiển Claude Code từ điện thoại.

Giữ script chạy nhanh

Script của bạn chạy sau mỗi message của assistant, và Claude Code hủy lần chạy đang thực hiện khi có update mới. Vì vậy, script chậm sẽ hiển thị text cũ hoặc không hiển thị gì.

Mỗi lần gọi jq chỉ mất vài mili giây. git mới là phần gây chậm: git status trong một repository lớn khi cache chưa có dữ liệu có thể mất hàng trăm mili giây. Script ở trên cố ý tránh git status và gọi git branch --show-current, lệnh này đọc .git/HEAD rồi trả về ngay.

Nếu thêm phần xử lý nặng hơn, hãy cache kết quả vào một file và refresh file đó vài giây một lần. Đặt session làm khóa cho file:

CACHE="/tmp/statusline-$(field '.session_id')"

Dùng session_id, không dùng $$. $$ là process ID của script, thay đổi trong mỗi lần chạy, nên cache dùng giá trị này làm khóa sẽ không bao giờ được hit và bạn phải chịu toàn bộ chi phí xử lý mỗi lần. session_id ổn định trong toàn bộ session và khác nhau giữa các session, nên 2 session Claude Code trong 2 repository không thể đọc branch name đã cache của nhau.

Còn một giới hạn cần biết: tput cols không hoạt động bên trong script statusline. Claude Code capture output thay vì attach script của bạn vào terminal, nên không có kích thước nào để đo. Từ v2.1.153 trở lên, Claude Code đặt các biến môi trường COLUMNSLINES trước khi chạy command. Vì vậy, hãy đọc $COLUMNS khi cần quyết định lượng output sẽ in ra.

Vì sao statusline vẫn trống

Hoàn toàn không hiển thị gì. Kiểm tra bit execute bằng ls -l ~/.claude/statusline.sh, rồi chạy script thủ công với input giả lập ở trên. Nếu script in ra một dòng trong shell nhưng không hiển thị trong Claude Code, trước tiên hãy dùng claude --debug. Lệnh này ghi exit code và stderr của lần chạy statusline đầu tiên trong session.

Log debug báo Status line command skipped: workspace trust not accepted. Statusline thực thi một shell command, nên cũng chịu workspace trust gate giống hooks. Cho đến khi bạn chấp nhận hộp thoại trust cho thư mục đó, command sẽ không chạy. Trường hợp này thường gặp trên VPS, nơi mỗi clone mới là một thư mục mà Claude Code chưa từng thấy. Khởi động lại Claude Code trong thư mục đó và chấp nhận hộp thoại.

Mọi thứ đều trống và disableAllHooks đã được thiết lập. "disableAllHooks": true trong settings.json cũng vô hiệu hóa statusline vì đây là cùng một shell-execution gate. Xóa tùy chọn này hoặc đặt thành false.

Dòng hiển thị null. Một jq selector đã truy cập đến key bị thiếu hoặc có giá trị null, và jq -r in null thành 4 ký tự null. Thêm // empty cho text và // 0 cho số.

Dòng bị trống ngay sau khi bạn sửa script. Một command thoát với mã khác 0 hoặc không in gì sẽ làm dòng bị trống. Nguyên nhân thường gặp là dòng cuối như [ -n "$BRANCH" ] && LINE="...". Dòng này thoát với mã 1 khi branch trống và làm toàn bộ script dùng cùng exit code đó. Giữ printf ở cuối hoặc thêm exit 0.

Escape code hiển thị thành text nguyên dạng, chẳng hạn \e]8;; trên thanh. Dùng printf '%b' thay cho echo -e. Các liên kết OSC 8 có thể nhấp cũng cần terminal hỗ trợ. tmux hoặc SSH có thể loại bỏ các sequence này, nên plain colour là lựa chọn an toàn hơn trên máy chủ remote.

Phía bên phải của dòng bị cắt. System notification và bộ đếm token ở verbose mode cùng dùng dòng đó từ phía bên phải, nên terminal hẹp sẽ bị chồng lấn. Giữ output ngắn. Nếu cần thống kê usage chính xác thay vì chỉ xem một con số trên thanh, xem cách Claude Code đếm token.

FAQ

Cài đặt statusline của Claude Code nằm ở đâu?

Cài đặt nằm trong settings.json, dưới dạng một block statusLine với type đặt thành "command"command đặt thành đường dẫn script hoặc shell command. Cài đặt cấp người dùng nằm tại ~/.claude/settings.json và áp dụng cho mọi project trên máy đó. Cài đặt cấp project nằm tại .claude/settings.json bên trong repository và được ưu tiên trong thư mục đó. Cài đặt tự reload, nhưng thay đổi chỉ hiển thị ở lần trigger cập nhật tiếp theo, chẳng hạn tin nhắn tiếp theo của bạn.

Vì sao statusline của Claude Code bị trống?

Gần như mọi trường hợp đều do một trong 4 nguyên nhân. Script không có quyền execute, nên shell trả về Permission denied và không có gì được ghi ra stdout. Hộp thoại xác nhận workspace trust chưa từng được chấp nhận, nên claude --debug ghi log Status line command skipped: workspace trust not accepted. disableAllHookstrue, khiến statusline bị tắt theo cùng cơ chế kiểm tra. Hoặc script thoát với mã khác 0, khiến dòng này bị trống. Trước tiên, hãy chạy thử thủ công: echo '{}' | ~/.claude/statusline.sh phải in ra nội dung.

JSON của statusline có chứa git branch không?

Không. JSON chứa trạng thái session như model, các thư mục workspace, số liệu context window và chi phí. JSON không chứa thông tin git. Branch hiển thị trên thanh của bạn đến từ script tự gọi git branch --show-current. Truyền thư mục từ JSON bằng git -C "$DIR" để branch luôn khớp với thư mục mà thanh trạng thái đang hiển thị.

Statusline có tốn token hoặc làm session chậm không?

Không tốn token vì script chạy cục bộ và output của script không bao giờ được gửi đến model. Tốc độ phụ thuộc vào bạn. Command chạy sau mỗi tin nhắn của assistant với debounce 300 ms. Claude Code hủy lần chạy đang thực hiện khi có lần cập nhật mới, nên script mất đủ 1 giây sẽ hiển thị nội dung cũ. Tránh dùng git status trong repository lớn và cache mọi thao tác chậm vào một file được định danh theo session_id.

Làm cách nào để hiển thị statusline khác nhau trên từng server?

Dùng một script duy nhất và để script đọc thông tin máy. Script trên in ra $HOSTNAME, dùng hostname -s làm giá trị fallback. Vì vậy, cùng một file được copy lên mọi máy vẫn gắn nhãn đúng cho từng máy, còn thủ thuật tô màu bằng checksum sẽ cấp cho mỗi hostname một màu riêng. Nếu một server cần layout khác, hãy đặt một block statusLine trong cài đặt project của repository bạn làm việc trên máy đó, vì cài đặt project được ưu tiên hơn cài đặt cấp người dùng trong thư mục đó.