Thứ Ba tuần trước, một khách hàng hỏi mình tại sao session Claude Code của họ đốt 40,000 token cho một task lẽ ra chỉ tốn 4,000. Mình không có câu trả lời tốt. Có log, nhưng chẳng có gì nói được tool call nào gây ra chuyện đó, ngay lúc nó xảy ra, trước khi hóa đơn về.

Nên khi Anthropic âm thầm ra mắt Mods tuần này — một TypeScript hook API cho phép đăng ký middleware vào vòng đời event của Claude Code — mình bỏ ngang việc đang làm, test ngay trên đúng repo của khách hàng đó.

Mod thực ra là gì

Bỏ qua cái tên marketing một lúc. Mod là một hàm bạn đăng ký vào một event có tên, và Claude Code gọi nó đồng bộ ngay tại điểm đó trong loop. Các event quan trọng với đa số người dùng: session.start, prompt.submit, tool.call, turn.start, turn.complete, command.run, và ui.render. Vậy là hết — bảy hook, không phải bảy mươi.

API có dạng register(on, options), và nếu bạn đã viết middleware Express hay Koa rồi, tay bạn tự biết phải gõ gì:

import { register } from "@anthropic-ai/claude-code-mods";

register("tool.call", {
  name: "cost-tracker",
  handler: async (ctx, next) => {
    const start = ctx.timestamp;
    const result = await next();
    const tokens = result.usage?.totalTokens ?? 0;

    if (tokens > 2000) {
      console.warn(
        `[cost-tracker] ${ctx.toolName} burned ${tokens} tokens in a single call`
      );
    }

    return result;
  },
});

Hai mươi phút, mình có một mod log mọi tool call vượt 2,000 token thẳng ra stderr. Thứ đầu tiên nó bắt được: một lệnh Read trên file log 40MB mà agent chộp lấy không giới hạn số dòng, ba lần khác nhau trong cùng một session, vì nó cứ quên là đã đọc rồi.

Đó không phải lỗi prompt. Đó là lỗi observability, và cuối cùng Claude Code cũng có một khe để giải quyết việc này mà không cần screen-scrape output của CLI như mình vẫn làm từ tháng Ba.

Chỗ nó gãy

Đây là phần bài announcement không nói cho bạn biết. tool.call fire trước khi model quyết định có retry tool bị lỗi hay không. Mình đăng ký thêm một mod ở turn.complete, nghĩ là nó sẽ cho mình một summary gọn gàng mỗi turn hội thoại — thực ra nó fire giữa luồng mỗi khi model dừng lại “suy nghĩ” giữa các tool call trong một chuỗi agentic dài, nghĩa là cost tracker của mình đếm trùng một turn logic có bốn vòng tool round-trip nội bộ.

Mình không tìm ra điều này trong docs. Mình phát hiện vì dashboard báo $340 chi phí ước tính cho một session mà console thật của Anthropic chỉ tính $38. Đây là lỗi middleware kinh điển, kiểu ai từng debug một next() Express bị gọi đúp đều từng gặp — chỉ khác là ở đây “request” là một turn model không xác định, nên không thể chỉ thêm idempotency key rồi xong. Mình phải key dedup logic theo ctx.turnId cộng với một số thứ tự tăng dần mà Anthropic expose trên context object — thứ mình không thấy ghi ở đâu cả, chỉ phát hiện ra khi console.log(ctx) để debug.

Phần mình thực sự thích

Mods không yêu cầu fork Claude Code hay chạy proxy trước API của Anthropic — cách mình vẫn làm để attribute chi phí trước đây: một reverse proxy ngu ngơ soi request/response body, mong manh và chậm. Giờ nó chạy in-process, có type, và đi kèm luôn trong CLI. Với một Tech Lead triển khai cho cả team, đó là khoảng cách giữa “đây là shell script, chúc may mắn” và “npm install cái này, đây là config của bạn”.

Mình vẫn gọi đây là bề mặt v0.1. Danh sách event còn mỏng — không có error, cũng không có hook context.compact dù compaction có thể là thứ tốn kém nhất trong một session dài. Nếu bạn cần phản ứng với áp lực context window, bạn phải poll ctx.contextUsage từ trong turn.complete — đó là hack, không phải feature.

Thứ mình sẽ ship ngay tuần này

Nếu bạn chạy Claude Code cho một team hơn ba người, viết mod cost-tracker ở trên ngay hôm nay. Hai mươi phút, và nó sẽ bắt được thứ gì đó đáng xấu hổ trong giờ đầu tiên — nó đã bắt được ở mình. Đừng vội làm bản phức tạp hơn trước khi bạn thấy agent của mình đang làm sai cái gì thật — của mình hóa ra chỉ là lỗi đọc lại file ngu ngơ, không phải vấn đề architecture gì cao siêu.

Mình chưa viết lại hệ thống proxy cũ. Lỗi đếm turn kia tốn của mình nguyên một buổi chiều, và mình muốn chờ thêm ít nhất một bản minor nữa trước khi tin turn.complete cho bất cứ thứ gì liên quan đến billing. Nhưng bảy hook này đúng là bảy hook cần có, và với một API v1, điều đó hiếm hơn bạn nghĩ.

Xuất nội dung

Bình luận