Cài statusline Claude Code trên VPS
Hiển thị hostname, thư mục, git branch và model ngay dưới prompt Claude Code, giúp bạn nhận đúng server trước khi chạy lệnh hoặc migration.
Claude Code statusline hiển thị gì
Claude Code statusline là một dòng bên dưới prompt, hiển thị output của một script do bạn viết. Bạn thêm một block statusLine vào settings.json rồi trỏ block đó đến một command. Claude Code chạy command này, gửi trạng thái session dưới dạng JSON vào standard input và in mọi nội dung command ghi ra 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 trong mọi terminal đều trông giống nhau. 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. Statusline bắt đầu bằng hostname sẽ loại bỏ kiểu nhầm lẫn 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 repository cũng hoạt động và được ưu tiên cho thư mục tương ứng.
{
"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 lệnh thông thường. 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 tháng 8 năm 2026, có 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 ở trạng thái idle. hideVimModeIndicator ẩn text -- INSERT -- tích hợp khi script của bạn đã tự render vim mode.
Script statusline nhận dữ liệu gì?
Không được tin vào danh sách field bạn đọc ở bất kỳ đâu, kể cả trang này. Hãy lấy object thực tế mà version 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.shTrỏ 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 được:
jq . /tmp/statusline-input.jsonBạn đã có cấu trúc chính xác cho build của mình và có thể lặp lại việc này bất cứ khi nào một bản update thay đổi cấu trúc.
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 id và display_name. workspace chứa current_dir và project_dir: current_dir là vị trí hiện tại của session, project_dir là nơi session được khởi chạy, và hai giá trị này khác nhau sau khi working directory thay đổi trong 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à duy nhất giữa các session, điều này hữu ích cho việc caching về sau.
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, worktree và effort 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 literal null, khiến bar hiển thị null cho người dùng. 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_percentage và context_window.current_usage là null trướ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 dùng // 0; nếu không, nó sẽ đọc null trong những 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 lấp đầy 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.
Một script statusline có cơ chế fallback thay vì bị lỗi
Đây là phiên bản có thể copy-paste. Script hiển thị hostname, thư mục làm việc, git branch và tên model. Mỗi trường đều có giá trị fallback, 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 giá trị đều được đọc qua field. Lệnh này thêm // empty, nên khi một key bị đổi tên hoặc xóa, kết quả là chuỗi rỗng và dòng tiếp theo sẽ cung cấp giá trị mặc định. Thư mục lần lượt fallback từ workspace.current_dir sang cwd rồi sang $PWD. Branch được lấy từ git -C "$DIR" thay vì dùng git độc lập, nên branch luôn khớp với thư mục mà bar đang hiển thị.
Lưu script, sau đó cấp quyền thực thi:
chmod +x ~/.claude/statusline.shQuyề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ẽ fail với Permission denied, không tạo stdout và khiến dòng này vẫn trống mà không hiển thị lỗi.
jq dùng để parse 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 jqSau đó trỏ setting đến script bằng block settings.json đầu tiên ở trên.
Kiểm thử script trước khi tin dùng
Chạy script 2 lần bằng tay. 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.shBạ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à git repository.
Tiếp theo là kiểm thử khi dữ liệu suy giảm. Đây là bước mọi người thường bỏ qua:
echo '{}' | ~/.claude/statusline.shMộ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 hostname, thư mục hiện tại lấy từ $PWD và từ claude tại vị trí lẽ ra là tên model. Không có lỗi crash và không in null. Script vượt qua kiểm thử này sẽ tiếp tục hoạt động khi một field bị đổi tên, vì với script, field bị đổi tên và field bị thiếu là cùng một sự kiện.
Bạn sẽ thấy gì
statusline hiển thị trên một hà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 bình thường, hàng này gồm: short hostname màu cyan, working directory với home directory được rút gọn thành ~, tên branch màu vàng nếu directory là một git repository, rồi tên model ở dạng mờ. Kết quả sẽ gần giống web-01 ~/api main Opus, với 4 phần đó được tô màu.
Hàng này chạy lại script khi session bắt đầu, kể cả khi resume; khi có assistant message mới; 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 cấu hình chu kỳ này. Các lần cập nhật được debounce trong 300 ms, nên một loạt thay đổi liên tiếp chỉ chạy script 1 lần. Thanh này ẩn trong lúc autocomplete, help menu và permission prompt hiển thị, sau đó xuất hiện lại.
Vì sao hostname phải đứng trước
Khi bạn duy trì 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ở một kết nối ssh thứ hai từ bên trong một pane tmux, title của cửa sổ thường vẫn giữ tên cũ vì title được đặt bởi một shell chưa biết rằng nó đã chuyển sang nơi khác. Để Claude Code chạy trong một tmux session detached 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 box.
Statusline khác ở chỗ nó được chính Claude Code render cho từng session, dựa trên dữ liệu mà session đó đang giữ. Nó không thể kế thừa từ pane sai hoặc giữ dữ liệu cũ vì một shell prompt chưa refresh. Nội dung hiển thị 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ép 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ột tên cụ thể luôn ánh xạ đến cùng một màu trong phạm vi 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.
Directory cũng cần được hiển thị vì cùng lý do đó. /srv/api và /srv/api-staging chỉ cách nhau một phím trong lệnh ssh, nhưng tác động có thể khác nhau hoàn toàn trong một incident. Model và branch là 2 thông tin còn lại đáng dành 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 không.
Màn hình nhỏ khiến các thông tin này càng quan trọng vì bạn không thể dựa vào title của cửa sổ. Nếu bạn dùng thiết lập 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. 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ị nội dung cũ hoặc không hiển thị gì.
Mỗi lần gọi jq chỉ mất vài milliseconds. git mới là phần chạ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 milliseconds. 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 thao tác 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 key của file theo session:
CACHE="/tmp/statusline-$(field '.session_id')"Dùng session_id, không dùng $$. $$ là process ID của script. Giá trị này khác nhau trong mỗi lần chạy, nên cache dùng nó làm key sẽ không bao giờ 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. Vì vậy, 2 session Claude Code trong 2 repository không thể đọc branch name đã cache của nhau. Theo thiết kế, các session được cô lập như vậy. Muốn một session chuyển công việc cho session khác cần thực hiện một bước rõ ràng. Đó là mục đích của gửi message từ một session Claude Code sang session khác.
Còn một giới hạn cần biết: tput cols không hoạt động bên trong statusline script. Claude Code capture output thay vì attach script của bạn vào terminal, nên không có gì để đo khi detect width. Từ v2.1.153 trở lên, Claude Code set các biến môi trường COLUMNS và LINES trước khi chạy command. Vì vậy, hãy đọc $COLUMNS khi cần quyết định lượng nội dung sẽ in ra.
Vì sao statusline vẫn để trống
Không hiển thị gì cả. Kiểm tra quyền execute bằng ls -l ~/.claude/statusline.sh, sau đó chạy script thủ công với input giả ở 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 kiểm tra claude --debug. Lệnh này ghi lại exit code và stderr của lần chạy statusline đầu tiên trong session.
Log debug ghi Status line command skipped: workspace trust not accepted. Statusline thực thi một shell command, nên chịu cùng workspace trust gate với hooks. Command sẽ không chạy cho đến khi bạn chấp nhận hộp thoại trust cho thư mục đó. 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 bật. "disableAllHooks": true trong settings.json cũng tắt 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. jq selector đã truy cập một 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 kết thúc với non-zero exit code 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="...". Lệnh này exit với code 1 khi branch rỗng và làm toàn bộ script sử dụ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 link OSC 8 có thể click 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ần bên phải của dòng bị cắt. System notification và token counter của verbose mode cùng sử dụng dòng này 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ì một con số trên thanh, xem cách Claude Code đếm token.
FAQ
Cấu hình statusline của Claude Code nằm ở đâu?
Cấu hình nằm trong settings.json, dưới dạng một block statusLine với type đặt thành "command" và command đặt thành đường dẫn script hoặc shell command. Cấu hình người dùng nằm tại ~/.claude/settings.json và áp dụng cho mọi project trên máy đó. Cấu hình project nằm tại .claude/settings.json trong repository và được ưu tiên cho thư mục đó. Cấu hình tự reload, nhưng thay đổi chỉ hiển thị ở lần kích hoạt cập nhật tiếp theo, chẳng hạn như 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 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, và claude --debug ghi log Status line command skipped: workspace trust not accepted. disableAllHooks là true, nên statusline bị vô hiệu hóa theo cùng cơ chế kiểm tra. Hoặc script thoát với mã khác 0, khiến dòng statusline bị trống. Hãy chạy thử thủ công trước: echo '{}' | ~/.claude/statusline.sh phải in ra nội dung.
JSON của statusline có bao gồm 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 statusline là do script của bạn 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 đang được statusline hiển thị.
Statusline có tốn token hoặc làm session chậm đi không?
Không tốn token, vì script chạy cục bộ và output của nó không bao giờ được gửi đến model. Tốc độ phụ thuộc vào script của 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ó cập nhật mới, nên script mất đủ một giây sẽ hiển thị nội dung cũ. Tránh dùng git status trong repository lớn, đồng thời cache dữ liệu chậm vào một file có key là 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 và để script tự đọc thông tin máy. Script ở trên in ra $HOSTNAME, dùng hostname -s làm giá trị dự phòng. Vì vậy, khi copy cùng một file lên mọi máy, script sẽ gắn nhãn đúng cho từng máy. Thủ thuật dùng màu theo checksum cũng 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ấu hình project của repository bạn làm việc trên máy đó, vì cấu hình project được ưu tiên hơn cấu hình người dùng đối với thư mục đó.