Bỏ qua tới nội dung chính
Quay lại tin tức

Tổ hợp phím Ctrl+C trong tác tử lập trình thực chất chỉ là byte 0x03. Chế độ raw mode quyết định bước xử lý tiếp theo.

Dev.to AI· Gabriel Anhaia· 3/11/2026opensource

Sách: AI That Ships Bộ sách: AI in TypeScript — gồm 5 cuốn, từ lệnh gọi LLM đầu tiên đến các tác tử (agent) trong môi trường vận hành thực tế — toàn bộ 5 cuốn tại đây Dự án: Hermes IDE | GitHub — môi trường phát triển tích hợp (IDE) dành cho các lập trình viên phát triển sản phẩm cùng Claude Code và các công cụ lập trình AI khác Tác giả: xgabriel.com | GitHub Khi nhấn tổ hợp phím Ctrl+C trong Claude Code, ba tình huống khác nhau có thể xảy ra. Tài liệu hướng dẫn chế độ tương tác liệt kê cả ba trường hợp này dưới cùng một thao tác: "Ngắt một tác vụ đang chạy. Nếu không có tác vụ nào đang hoạt động, lần nhấn đầu tiên sẽ xóa nội dung nhập của lời nhắc (prompt) và lần nhấn thứ hai sẽ thoát khỏi Claude Code." Một tín hiệu Unix thông thường không thể đưa ra lựa chọn này. Trong một shell thông thường, tổ hợp phím Ctrl+C không bao giờ re

Sách: AI That Ships Bộ sách: AI in TypeScript — 5 tập, từ lệnh gọi LLM đầu tiên đến các agent trong môi trường vận hành thực tế — trọn bộ 5 tập tại đây Dự án: Hermes IDE | GitHub — môi trường phát triển tích hợp (IDE) dành cho các lập trình viên triển khai sản phẩm với Claude Code cùng các công cụ lập trình AI khác Tác giả: xgabriel.com | GitHub Khi nhấn tổ hợp phím Ctrl+C trong Claude Code, ba tình huống khác nhau có thể xảy ra. Tài liệu hướng dẫn chế độ tương tác liệt kê cả ba trường hợp ứng với một tổ hợp phím duy nhất: "Ngắt một tác vụ đang chạy. Nếu không có tác vụ nào đang chạy, lần nhấn đầu tiên sẽ xóa nội dung câu lệnh đầu vào (prompt) và lần nhấn thứ hai sẽ thoát khỏi Claude Code." Một tín hiệu Unix thông thường không thể tự đưa ra lựa chọn đó. Trong một shell tiêu chuẩn, tổ hợp phím Ctrl+C không bao giờ được gửi tới chương trình dưới dạng một phím bấm. Nhân hệ điều hành (kernel) chuyển đổi thao tác này thành tín hiệu SIGINT và phân phối tới mọi tiến trình thuộc nhóm tiến trình tiền cảnh (foreground), do đó chương trình hoàn toàn không thể nhận biết liệu người dùng đã nhập dở một câu lệnh hay chưa. Vì vậy, khi một agent lập trình xóa bản nháp ở lần nhấn đầu tiên và thoát ứng dụng ở lần nhấn thứ hai, cơ chế mặc định nêu trên đã bị vô hiệu hóa. Cơ chế thực hiện việc này thường là chế độ raw (raw mode). Chế độ raw cũng là nguyên nhân khiến phím Esc, tổ hợp Ctrl+C hay thao tác đóng khung cửa sổ (pane) mang lại những phản ứng khác nhau. Cơ chế hoạt động của Ctrl+C trong shell thông thường Các thao tác phím đều đi qua trình điều khiển thiết bị đầu cuối (terminal driver) của nhân hệ điều hành trước khi bất kỳ chương trình nào tiếp nhận. Chương quy định giao diện thiết bị đầu cuối của chuẩn POSIX xác định nguyên tắc: ký tự INTR "tạo ra tín hiệu SIGINT và tín hiệu này được gửi đến toàn bộ các tiến trình trong nhóm tiến trình tiền cảnh" của thiết bị đầu cuối đó. Quy tắc này chỉ được áp dụng dưới một cờ cấu hình nhất định: "Nếu cờ ISIG được thiết lập, mỗi ký tự đầu vào sẽ được đối chiếu với các ký tự điều khiển đặc biệt INTR, QUIT và SUSP." Bản thân ký tự này có dung lượng một byte. Trong bảng mã ASCII, ký tự C có mã 0x43 và nằm cùng hàng với ký tự ETX ("kết thúc văn bản") tại mã 0x03. Phím Ctrl xóa các bit bậc cao, do đó tổ hợp Ctrl+C gửi byte 0x03 qua đường truyền dữ liệu. Trang hướng dẫn termios(3) mô tả tiến trình xử lý byte này khi cờ ISIG được bật: VINTR sẽ "gửi một tín hiệu SIGINT. Được nhận diện khi cờ ISIG được thiết lập, và sau đó không được chuyển tiếp làm dữ liệu đầu vào." Có thể kiểm tra hai thiết lập này thông qua lệnh stty: stty -a Cần tìm kiếm mục intr = ^C (byte kích hoạt tín hiệu) và cờ isig trong danh sách các cờ cấu hình. Dấu trừ ở đầu (-isig) biểu thị cờ này đang tắt. Sau đó, có thể tắt cờ này trong 5 giây để kiểm nghiệm: stty -isig; sleep 5; stty isig Nhấn Ctrl+C trong khi lệnh sleep đang thực thi. Tiến trình không bị gián đoạn. Khi cờ ISIG bị tắt, byte 0x03 được đưa vào hàng đợi đầu vào như một ký tự thông thường mà lệnh sleep không đọc tới, và lệnh sleep hoàn thành trọn vẹn thời gian chạy 5 giây. Lệnh stty isig ở cuối sẽ khôi phục lại trạng thái ban đầu của tín hiệu. Khi cờ ISIG bật, nhân hệ điều hành chuyển đổi byte 0x03 thành tín hiệu SIGINT cho toàn bộ tác vụ tiền cảnh. Khi cờ ISIG tắt, chương trình trực tiếp nhận byte dữ liệu và quyết định phương thức xử lý. Chế độ raw chuyển byte dữ liệu trực tiếp cho ứng dụng Một giao diện người dùng dòng lệnh (terminal UI) yêu cầu tiếp nhận từng phím bấm ngay tại thời điểm thao tác, không qua xử lý chỉnh sửa dòng lệnh và không hiển thị phản hồi ký tự (echo). Đó là đặc tính của chế độ raw, đi kèm với việc tắt cờ ISIG. Hàm cfmakeraw() của thư viện glibc, theo tài liệu termios(3), tiến hành xóa các cờ cục bộ ECHO | ECHONL | ICANON | ISIG | IEXTEN. Thư viện libuv mà Node sử dụng cũng thực hiện thao tác tương tự trong chế độ raw: tmp.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);. Tài liệu về tty của Node nêu rõ đối với phương thức setRawMode(): "Tổ hợp phím Ctrl+C sẽ không còn phát tín hiệu SIGINT khi ở chế độ này." Tài liệu quản lý tiến trình (process) cũng tái khẳng định: SIGINT "không được tạo ra khi chế độ terminal raw được kích hoạt và tổ hợp Ctrl+C được sử dụng." Dưới đây là đoạn mã tái lập thủ công hành vi của agent đã đề cập ở phần đầu bài viết. Lưu tệp dưới tên raw.mjs và thực thi lệnh node raw.mjs trong môi trường terminal thực tế: // raw.mjs: Ctrl+C được gửi dưới dạng byte dữ liệu, chương trình tự định nghĩa hành vi xử lý const { stdin } = process; stdin.setRawMode(true); let draft = ''; let armed = false; stdin.on('data', (buf) => { for (const b of buf) { const hex = '0x' + b.toString(16).padStart(2, '0'); if (b !== 0x03) { armed = false; draft += String.fromCharCode(b); console.log(hex, JSON.stringify(draft)); } else if (draft) { draft = ''; armed = true; console.log(hex, 'Ctrl+C: cleared, again to exit'); } else if (armed) { stdin.setRawMode(false); process.exit(0); } else { armed = true; console.log(hex, 'Ctrl+C: press again to exit'); } } }); Khi nhập một vài ký tự rồi nhấn tổ hợp phím Ctrl+C, nội dung bản nháp sẽ bị xóa và tiến trình tiếp tục chạy. Nếu nhấn thêm một lần nữa, tập lệnh sẽ kết thúc. Trong trường hợp bản nháp trống, lần nhấn đầu tiên sẽ kích hoạt trạng thái chuẩn bị thoát và lần nhấn thứ hai sẽ đóng tập lệnh. Phím Enter xuất hiện dưới dạng mã 0x0d thay vì 0x0a, do chế độ raw mode (chế độ thô) của libuv cũng xóa bỏ cờ ICRNL - cờ có chức năng chuyển đổi mã này. Nhằm mục đích rút gọn, tập lệnh xử lý các byte dưới dạng ASCII, do đó các chữ cái có dấu sẽ hiển thị không chuẩn xác. Khi nhấn phím Esc, hệ thống ghi nhận mã 0x1b. Khi nhấn một phím mũi tên, hệ thống ghi nhận mã 0x1b cùng các byte tiếp theo. Sự trùng lặp này là lý do các ứng dụng dòng lệnh (terminal) phải tạm dừng một khoảng thời gian ngắn sau khi nhận phím Esc trước khi xử lý, và cũng là nguyên nhân tmux có tùy chọn escape-time quy định thời gian "chờ sau khi phím escape được nhập để xác định xem đó có phải là một phần của chuỗi phím chức năng hoặc phím bổ trợ (meta) hay không". Phím Esc hoàn toàn không thể là tín hiệu cấp nhân (kernel signal), do đó trong mỗi tác tử (agent), phím này mang ý nghĩa do ứng dụng tự quy định. Tài liệu hướng dẫn của Claude Code cho biết phím Esc dùng để "dừng phản hồi hiện tại hoặc lệnh gọi công cụ giữa chừng nhằm điều hướng lại. Claude vẫn giữ nguyên kết quả xử lý đã thực hiện trước đó". Cơ chế hoạt động của tác tử có thể được kiểm tra trực tiếp mà không cần dựa vào tài liệu kỹ thuật. Bằng cách chạy lệnh tty trong khung điều khiển của tác tử trước khi khởi động, sau đó từ một khung thứ hai, kiểm tra các cờ của terminal đó khi tác tử đang ở dấu nhắc lệnh. Lệnh stty của GNU sử dụng tham số -F DEVICE; macOS và các hệ điều hành BSD sử dụng -f (FreeBSD stty): stty -a -F /dev/pts/3 # Linux, sử dụng đường dẫn tty tương ứng stty -a -f /dev/ttys003 # macOS Nếu xuất hiện cờ -isig, tổ hợp phím Ctrl+C được ứng dụng tiếp nhận dưới dạng một byte dữ liệu thông thường. Nếu xuất hiện cờ isig, nhân hệ điều hành vẫn chuyển đổi Ctrl+C thành tín hiệu SIGINT, và ứng dụng sẽ tự xử lý tín hiệu đó hoặc để tín hiệu này chấm dứt tiến trình. Tiến trình npm test do tác tử khởi tạo Giả định trường hợp tác tử thực thi lệnh npm test, bộ kiểm thử bị treo và người dùng nhấn tổ hợp phím Ctrl+C. Trong môi trường shell thông thường, thao tác phím này sẽ gửi tín hiệu SIGINT đến toàn bộ tác vụ đang chạy ở chế độ tiền cảnh (foreground). Ở chế độ raw mode, nhân hệ điều hành không gửi bất kỳ tín hiệu nào. Bộ kiểm thử vẫn tiếp tục vận hành cho đến khi tác tử chủ động chấm dứt, do vậy mỗi tác tử phải tự thiết lập cơ chế hủy bỏ riêng. Tài liệu tham khảo về các công cụ của Claude Code nêu rõ: "công cụ Bash thực thi mỗi lệnh trong một tiến trình riêng biệt". Tài liệu về chế độ tương tác của công cụ này chỉ cho biết Ctrl+C "làm gián đoạn một hoạt động đang diễn ra" mà không nêu cụ thể tín hiệu nào được chuyển đến tiến trình con. Dù vậy, tài liệu có mô tả các phương thức kết thúc khác cho tiến trình đó. Tổ hợp phím Ctrl+B dùng để chuyển một lệnh đang chạy

Nguồn tin: Dev.to AI — Tác giả: Gabriel Anhaia. Bản dịch tiếng Việt do AI thực hiện, có thể có sai sót.