---
title: "Nhận thông báo khi một lệnh chạy lâu trong terminal kết thúc"
description: "Đừng ngồi canh một bản build bốn mươi phút. Thêm một dòng sau bất kỳ lệnh nào là kết quả sẽ về điện thoại, kể cả khi bạn đã đi chỗ khác."
date: 2026-09-09
author: Nooc
authorAvatarLink: /images/avatars/nooc.webp
authorLink: https://nooc.me
tags:
  - terminal
  - shell
  - thông báo build
  - trợ lý lập trình AI
  - thông báo webhook
---

# Nhận thông báo khi một lệnh chạy lâu trong terminal kết thúc

Bạn khởi động một lượt huấn luyện, một bộ test đầy đủ, một lệnh `docker build`, hoặc một trợ lý lập trình AI đang xử lý một tác vụ dài. Sau đó bạn chỉ còn hai lựa chọn đều tệ: ngồi nhìn thanh tiến trình, hoặc bỏ đi rồi hai mươi phút sau quay lại và phát hiện nó đã hỏng ngay ở giây thứ chín mươi.

Còn một lựa chọn thứ ba, và nó chỉ dài một dòng.

## Cái dòng đó

Tạo một kênh trong Echobell, sao chép URL webhook của nó, rồi nối vào sau thứ bạn đang chạy:

```bash
pnpm build; curl -sS -X POST https://hook.echobell.one/t/TOKEN_CUA_BAN \
  -H 'content-type: application/json' \
  -d '{"title":"build xong","body":"echobell-web"}'
```

Chú ý là `;` chứ không phải `&&`. Với `&&`, thông báo chỉ bắn khi lệnh chạy thành công, mà như vậy là ngược hẳn: thất bại mới là trường hợp bạn muốn biết nhất.

## Gửi kèm cả mã thoát

Một thông báo chỉ nói "xong rồi" mới kể được nửa câu chuyện. Hãy bắt luôn trạng thái:

```bash
pnpm build; s=$?; curl -sS -X POST https://hook.echobell.one/t/TOKEN_CUA_BAN \
  -H 'content-type: application/json' \
  -d "{\"title\":\"build $([ $s -eq 0 ] && echo ok || echo THAT_BAI)\",\"status\":\"$s\"}"
```

`s=$?` phải nằm ngay sau lệnh: bất cứ thứ gì chen vào giữa, kể cả chính phép kiểm tra `[`, đều sẽ ghi đè `$?`.

## Biến nó thành một hàm shell

Gõ lại chừng đó mỗi lần thì còn gì là tiện. Hãy bỏ đoạn này vào `~/.zshrc` hoặc `~/.bashrc`:

```bash
notify() {
  "$@"
  local status=$?
  curl -sS -X POST "$ECHOBELL_HOOK" \
    -H 'content-type: application/json' \
    -d "{\"command\":\"$*\",\"status\":\"$status\",\"host\":\"$(hostname -s)\"}" \
    >/dev/null
  return $status
}
```

Đặt `ECHOBELL_HOOK` trong profile của shell — hoặc tốt hơn, đừng để nó trong kho dotfiles chút nào mà export từ một tệp bạn không commit. Rồi thì:

```bash
notify pnpm test
notify cargo build --release
notify python train.py
```

Dòng `return $status` ở cuối rất quan trọng: nó giữ cho `notify` trong suốt, nên `notify make && ./deploy.sh` vẫn hành xử đúng như bạn mong đợi.

Với payload đó, template của kênh có thể viết là:

**Tiêu đề**

```
{{command}} · {{status}}
```

**Nội dung**

```
trên {{host}}
```

## Chỉ đổ chuông khi thất bại

Phần lớn các lượt chạy đều thành công và bạn chẳng cần nghe về chúng. Hai kênh giải quyết gọn việc này: một kênh thường nhận tất cả, và một kênh gọi có điều kiện:

```
status != "0"
```

Hãy trỏ `notify` sang kênh thứ hai cho những thứ đáng để bạn bật dậy khỏi giường, còn các lượt chạy thành công thì cứ lặng lẽ trôi qua.

## Sống sót qua việc gập máy và mất kết nối SSH

Nếu lệnh đang chạy qua SSH, gập máy sẽ giết shell và thông báo không bao giờ được gửi. Có hai cách chữa:

**tmux** — khởi động lệnh bên trong một phiên rồi tách ra:

```bash
tmux new -d -s build 'notify pnpm build'
```

**nohup** — cho một lần dùng:

```bash
nohup bash -c 'notify pnpm build' >/dev/null 2>&1 &
```

Cách nào thì tiến trình cũng sống lâu hơn kết nối của bạn, và thông báo vẫn tới dù bạn còn nối máy hay không.

## Trợ lý lập trình AI

Cùng mô hình đó áp dụng được cho một trợ lý dòng lệnh đang xử lý tác vụ dài:

```bash
notify codex exec "refactor module thanh toán rồi chạy test"
```

Với những trợ lý dừng giữa chừng để chờ người — chờ phê duyệt, chờ cấp quyền — thì thông báo hoàn tất là công cụ sai, vì lượt chạy đó chưa hoàn tất. Trường hợp ấy cần hook hoặc callback của chính trợ lý, và xứng đáng một cuộc gọi hơn là một thông báo đẩy. [Biến các cửa phê duyệt của trợ lý thành cuộc gọi](/vi/blog/ai-agent-human-in-the-loop-alerts) nói đúng về việc này, kể cả hook `Notification` của Claude Code và matcher `agent_needs_input` của nó.

Dùng cả hai: hook cho "tôi đang kẹt", hàm shell cho "tôi xong rồi".

## Đừng đưa gì vào thông báo

**Đừng đưa output của lệnh.** Rất dễ nảy ý nhét vài dòng cuối của một bản build hỏng vào phần nội dung. Hãy kìm lại: log build chứa token, chuỗi kết nối và dữ liệu khách hàng thường xuyên hơn nhiều người tưởng, mà nội dung thông báo thì rốt cuộc hiện lên màn hình khoá. Gửi mã thoát thôi, rồi tự quay lại terminal mà xem.

**Đừng đưa URL của kênh vào kho dotfiles công khai.** Ai có URL đó cũng gửi được vào kênh của bạn. Hãy giữ nó trong một tệp không đưa vào quản lý phiên bản, và bật **Chỉ POST** để một lần xem trước liên kết lạc lối không kích hoạt được nó.

## Câu hỏi thường gặp

### Sao không dùng `terminal-notifier` hay `notify-send`?

Những công cụ đó hiện thông báo trên chính máy đang chạy lệnh. Điều đó ổn khi bạn đang ngồi ở đó. Cả điểm của cách làm này là dành cho lúc bạn không ngồi đó: một máy từ xa, một chiếc laptop đã gập, một căn phòng khác.

### Trên Windows có được không?

Mô hình thì được, cú pháp thì khác. Trong PowerShell, tương đương là `Invoke-RestMethod -Method Post -Uri $env:ECHOBELL_HOOK -ContentType application/json -Body $json`, và dùng `$LASTEXITCODE` thay cho `$?`.

### Thông báo lên đồng hồ được không?

Được. Đăng ký sẽ gửi tới chiếc Apple Watch đã ghép đôi, mà thật ra đó mới là hình thức hợp lý nhất cho câu "build xong rồi".

### Nếu lệnh chạy 12 tiếng thì sao?

Không có gì hết hạn trong cách làm này — `curl` chạy vào đúng lúc lệnh trả về. Hãy chạy dưới tmux để một lần rớt mạng không kéo tiến trình đi theo.

### Đồng đội của tôi có nhận được cùng thông báo không?

Có. Chia sẻ kênh và mỗi người đăng ký tự chọn kiểu thông báo. Rất tiện trên một máy huấn luyện dùng chung, nơi có hơn một người quan tâm rằng GPU đã rảnh trở lại.

## Kết lại

Một hàm shell, một kênh, và thêm một điều kiện nếu bạn chỉ muốn nhận các lần thất bại. Mất chừng hai phút để dựng, và trả lại cho bạn hai mươi phút vẫn dành để nhìn thanh tiến trình.

[Tải Echobell cho iPhone](https://apps.apple.com/app/apple-store/id6743597198?pt=128151925&ct=blog-notify-when-terminal-command-finishes-vi&mt=8) hoặc [lấy trên Google Play](https://play.google.com/store/apps/details?id=one.echobell.echobellandroid), rồi thử với một thứ đủ lâu để bạn phải rời chỗ.

---

## Nội dung liên quan

- [Biến các cửa phê duyệt của trợ lý thành cuộc gọi](/vi/blog/ai-agent-human-in-the-loop-alerts)
- [Đừng bỏ lỡ một lần GitHub Actions thất bại](/vi/blog/github-actions-notifications)
- [Cảnh báo khi cron job thất bại](/vi/blog/cron-job-failure-alerts)
- [Tài liệu về trigger webhook](/vi/docs/webhook)
- [Tham khảo điều kiện kênh](/vi/docs/conditions)
