Skip to content

Dùng credential broker ​

Khi manifest khai báo broker:, bạn không giữ chuỗi kết nối DB, mật khẩu Redis, token Grafana hay key PostHog nào trên máy: kit đổi GitHub token của bạn lấy một JWT sống 15 phút, rồi gọi broker. Trang này là phần việc phía dev. Phía maintainer dựng host xem Dựng host credential broker.

Manifest không khai báo broker: thì mọi lệnh kit chạy như cũ, đọc credential từ <workspace>/.zenify/.env (Credential dev với zenify env). Manifest nhúng sẵn trong binary khai báo broker: cho workspace zenify, nên ở workspace này các lệnh dưới đây đi qua broker.

Manifest nào quyết định broker ​

GitHub token của bạn được gửi tới URL broker, nên kit chỉ lấy broker: từ hai nguồn: manifest nhúng sẵn trong binary, hoặc file bạn chỉ rõ bằng --manifest. Kit không bao giờ đọc broker: từ manifest/repos.yaml ở thư mục hiện tại, và overlay cá nhân không đổi được broker:. Một --manifest không tồn tại hoặc sai là lỗi; lệnh không lùi về credential trên máy. Token cache ở ~/.zenify/broker/token.json ghi kèm URL broker đã cấp, nên token của broker này không bao giờ gửi sang broker khác.

Cài một lần ​

  1. Đăng nhập GitHub CLI với scope read:org.

    bash
    gh auth login -s read:org

    Đã đăng nhập từ trước mà thiếu scope thì chạy gh auth refresh -s read:org. Broker kiểm tra bạn thuộc org qua GitHub token này; không thuộc org thì broker từ chối và không cấp JWT.

  2. Chạy zenify doctor để kiểm tra broker đã dùng được chưa.

zenify doctor mục broker ​

Mục broker kiểm tra lần lượt: gh đã đăng nhập, gh có scope read:org, broker trả lời /healthz trong 5 giây, rồi đổi được token. Bước đổi token luôn xin JWT mới, không dùng cache, nên doctor thật sự kiểm tra lại bạn còn trong org. Thành công in broker ok, login=<login broker cấp token>, role=<lead|dev|agent>; doctor không bao giờ in token. Khi lỗi, thông báo nói bước nào hỏng:

Thông báoViệc cần làm
gh chưa đăng nhập — chạy `gh auth login` Đăng nhập gh như trên
gh thiếu scope read:org — chạy `gh auth refresh -s read:org` Chạy lệnh được gợi ý
không tới được broker — kiểm tra mạng và broker.url trong manifest rồi chạy lạiMở https://broker.zenify.vn/healthz trên trình duyệt; không mở được thì báo maintainer
broker từ chối token: <status> not_org_member: <lời nhắn>GitHub token chưa thấy bạn trong org: kiểm tra scope read:org, và nếu org bật SSO thì authorize token của GitHub CLI cho org trên GitHub, rồi chạy lại
broker từ chối token: 403 not_allowed: …Bạn thuộc org nhưng không thuộc team leaders hay zenify-team; nhờ maintainer thêm vào team đúng
broker từ chối token: 403 login_denied: …Login này là tài khoản dùng chung, broker không cấp token; đăng nhập gh bằng tài khoản cá nhân
broker từ chối token: <status> rate_limited: … hoặc … github_unavailable: …Broker hoặc GitHub đang tạm quá tải; đợi một lát rồi chạy lại
broker cấu hình sai, báo người quản trị broker: <lời nhắn>Lỗi ở phía host broker, không phải máy bạn; gửi nguyên dòng thông báo cho maintainer
broker từ chối token: <status> <mã khác>: <lời nhắn>Hỏi maintainer, kèm nguyên dòng thông báo

Muốn tự kiểm tra riêng bước đổi token: zenify broker token in JWT ra stdout và login=<login>, role=<role> ra stderr (zenify broker headers cũng in một JWT). Đừng dán kết quả vào chat hay log. Khi broker có, mục tools của doctor không đòi mongosh và mysql.

Broker từ chối token đang cache ​

zenify broker headers (lệnh Claude Code gọi cho zenify-grafana, zenify-posthog và zenify-zenie) dùng JWT đang cache tới khi nó hết hạn, và Claude Code không báo lại cho kit khi broker trả 401. Nếu MCP báo lỗi đăng nhập trong khi zenify doctor mục broker đã ok, chạy zenify broker token --refresh để bỏ cache và xin JWT mới, rồi khởi động lại Claude Code.

Lead và dev ​

Broker cấp role theo team GitHub trong org: thuộc team leaders là lead, không thì thuộc team zenify-team là dev. Org member không thuộc team nào trong hai team bị từ chối (not_allowed).

  • Lead đọc như mọi lệnh ở trang này.
  • Dev trên staging đọc như lead. Trên production (--prd), dev chỉ thấy cấu trúc và plan, không thấy nội dung bản ghi:
    • db-read: collections, count không có --filter (tổng số ước lượng qua estimatedDocumentCount; count có --filter chỉ dành cho lead), explain ở mức queryPlanner (chỉ in plan, không chạy truy vấn nên không có executionStats), indexes, stats, tables, và sql dạng EXPLAIN [FORMAT=…] SELECT … (đúng một SELECT, không subquery, không ANALYZE, không WITH), DESCRIBE/DESC <bảng>, SHOW INDEX/SHOW INDEXES/SHOW KEYS, SHOW CREATE TABLE, SHOW TABLE STATUS, SHOW TABLES, SHOW COLUMNS. Câu SHOW/DESCRIBE chỉ gồm từ khoá, tên bảng hoặc database và LIKE '<chuỗi>'; có WHERE, ( hay SELECT thì bị từ chối. Database audit log (target=audit) chỉ dành cho lead.
    • redis-read: chỉ TTL, TYPE, EXISTS, SCAN, LLEN, SCARD, ZCARD.
    • Truy vấn lỗi chỉ báo query failed; error detail is withheld for role dev on prd, vì thông báo lỗi của database có thể trích nội dung bản ghi. Stderr của mongosh/mysql cũng không được trả về khi truy vấn thành công. Cần xem lỗi chi tiết thì chạy lại trên staging.
    • Giới hạn này là rào chắn để agent không vô tình đọc dữ liệu production, không phải ranh giới chống người cố tình dò dữ liệu: EXPLAIN của MySQL vẫn đọc trước dòng khớp khoá chính khi lập plan.
  • MCP Grafana, PostHog và tài khoản E2E mở cho cả hai role.

Role agent (worker không người) ​

Worker chạy không người dùng GitHub App thay cho tài khoản cá nhân, và broker cấp role agent nếu bot của App nằm trong agent_ids (Dựng host credential broker). Phía client có hai biến môi trường:

  • GH_TOKEN: khi có giá trị, kit dùng nó làm GitHub token để đổi JWT; không có thì vẫn gọi gh auth token như trước. Đặt installation token (ghs_) của App vào đây. Token cache ghi JWT được xin bằng nguồn nào (src: agent khi GH_TOKEN bắt đầu bằng ghs_, còn lại user); tiến trình khác nguồn coi như không có cache và xin JWT mới, nên JWT của lead trên cùng máy không bao giờ phục vụ worker, và ngược lại.
  • ZNF_TASK_ID: nếu khớp ^[A-Za-z0-9._-]{1,64}$, kit gửi nó trong header X-Zenify-Task ở mọi request tới broker và broker ghi vào field task của audit. Giá trị không hợp lệ bị bỏ qua.

agent đọc production như lead và ghi staging như lead, nhưng không ghi được production và không lấy được credential lumi-agent của production (zenify data run --prd --lumi bị 403 lead_only, vì đó là credential đọc-ghi của chính lumi-agent). zenify data run --prd --write ở role agent (role agent trong cache, hoặc GH_TOKEN là token ghs_) dừng ngay (sau khi kiểm dry-run như thường lệ) và in lệnh cần chạy để chuyển cho lead: zenify await data-write --summary '<what it changes>' --command '<lệnh cho lead>', trong đó lệnh cho lead là cd <thư mục tuyệt đối> && <lệnh dry-run> && zenify data run --prd --write -- …, chạy được nguyên văn trên máy của lead. Xem Chế độ không người.

Lệnh vượt quyền của dev báo lệnh này chỉ dành cho lead (team leaders); dev trên prd chỉ dùng db-read: collections, count (no --filter), explain, indexes, stats, tables, sql: EXPLAIN SELECT without subquery, DESCRIBE <table>, SHOW INDEX|COLUMNS|TABLES|CREATE TABLE|TABLE STATUS; redis-read: EXISTS LLEN SCAN SCARD TTL TYPE ZCARD và không chạy truy vấn nào. Danh sách này do CLI tự in từ bảng quyền của chính nó, nên khớp với broker cùng phiên bản; broker cũ hơn hoặc mới hơn CLI có thể áp danh sách khác, khi đó nâng cấp cả hai lên cùng phiên bản. Khi token cache (~/.zenify/broker/token.json) ghi role dev, kit từ chối ngay tại máy, không gọi broker; chưa có cache thì broker quyết định. Đầu mỗi phiên Claude Code, hook SessionStart đọc cache này (không gọi mạng). Với dev nó in một dòng broker: login=<login> role=dev — prd: … để agent biết trước giới hạn production. Với mọi role đã có cache, nó in thêm một dòng data: agent tự chạy zenify data run --stg|--prd [--write] -- <lệnh> (dry-run trước; --prd --write chờ "ok" của người dùng; xem _shared/data-scripts.md), dev thêm hậu tố ; prd: cần lead. Giới hạn chỉ đọc của db-read và redis-read áp cho mọi người, kể cả lead; việc ghi chỉ đi qua zenify data run. zenify doctor mục broker và zenify broker token in role hiện tại; vừa được thêm vào team leaders thì chạy zenify broker token --refresh để xin JWT mang role mới.

Đọc database: db-read ​

Cùng lệnh và cùng giới hạn chỉ đọc như Đọc dữ liệu thật; khi có broker, truy vấn đi qua broker. Thêm --prd để đọc production:

bash
zenify db-read --prd count chat_rooms
zenify db-read --prd sql 'DESCRIBE agents'
zenify db-read --prd indexes chat_rooms
zenify db-read --prd stats chat_rooms

Cờ --target audit (chỉ Mongo và chỉ production, chỉ dành cho lead) đọc database audit log thay cho database chính; dùng với --prd, ví dụ zenify db-read --prd --target audit collections. Cờ --target lumi đọc MySQL của lumi-agent (db_chatbot trên production, mass_chatbot_v2 trên staging), chỉ sql và tables; trên production chỉ dành cho lead, ví dụ zenify db-read --prd --target lumi sql 'SELECT COUNT(*) FROM goals'. Cờ --target msghub đọc Mongo của message-hub (database đặt bằng mongo.msghub_db của broker), nhận mọi lệnh Mongo, không nhận sql/tables; trên production chỉ dành cho lead, ví dụ zenify db-read --prd --target msghub count <collection>. Riêng với --target lumi và --target msghub, dev chỉ bị từ chối trên production, còn staging vẫn mở. Broker chưa cấu hình MSGHUB_MONGO_URL_* của env đó thì lệnh báo broker_misconfigured. --target audit không kèm --prd hay dùng với truy vấn MySQL cũng bị từ chối. Qua broker, lỗi kết nối không còn gợi ý .zenify/.env hay tên biến MONGO_URL_PRD, vì bạn không giữ chuỗi kết nối nào.

Kit vẫn kiểm tra tham số ở máy bạn như cũ trước khi gửi lên broker.

Đọc Redis: redis-read ​

bash
zenify redis-read GET some:key
zenify redis-read --prd LRANGE some:list 0 49
  • Chỉ các lệnh chỉ đọc sau được phép: GET MGET HGET HGETALL HKEYS LRANGE LLEN SMEMBERS SCARD ZRANGE ZCARD XRANGE TTL TYPE EXISTS SCAN. Lệnh khác bị từ chối ngay ở máy bạn (mã 2), không gửi lên broker.
  • SCAN tự thêm COUNT 100 nếu thiếu; COUNT phải trong khoảng 1 đến 1000.
  • Broker giới hạn mỗi lần đọc: LRANGE, ZRANGE, XRANGE tối đa 200 phần tử; kết quả tối đa 1 MiB: GET và các lệnh khác bị cắt và lệnh ghi thông báo ra stderr, còn MGET, HGET, HGETALL, SMEMBERS vượt mức thì bị từ chối; MGET tối đa 100 key; HGETALL, HKEYS, SMEMBERS từ chối key có hơn 10000 phần tử.
  • Cờ (--prd) phải đứng trước lệnh Redis, để tham số như -1 không bị đọc thành cờ. --prd đứng sau lệnh (ví dụ zenify redis-read GET k --prd) bị từ chối ngay ở máy bạn với mã 2 (BadArgs), không gửi request nào, vì nếu không --prd sẽ thành tham số Redis và lệnh chạy nhầm trên staging.
  • Kết quả LRANGE, ZRANGE, XRANGE bị cắt ở 200 phần tử thì lệnh in zenify redis-read: kết quả đã bị cắt bớt ra stderr; stdout chỉ chứa dữ liệu. Không có thông báo nghĩa là danh sách đã trả đủ, kể cả khi đúng 200 phần tử.
  • Không có broker thì lệnh thoát mã 2 với "redis-read cần broker".

Chạy script với credential broker: data run ​

bash
zenify data run --stg -- node docs/scripts/contact-center-be/fix-tags.js --dry-run
zenify data run --stg --write -- node docs/scripts/contact-center-be/fix-tags.js

zenify data run xin credential từ broker rồi chạy lệnh sau -- với các biến MONGO_URL, MYSQL_URL, REDIS_URL (và ELASTIC_URL khi broker có cấu hình, chỉ ở chế độ đọc; thêm LUMI_MYSQL_URL, QDRANT_URL, LUMI_APP_URL khi có --lumi). Bạn không giữ chuỗi kết nối nào trong shell. Agent tự chạy lệnh này, không cần bạn gõ !.

  • Phải chọn đúng một trong --stg hoặc --prd; không thì thoát mã 2. --prd chỉ dành cho lead.
  • Lệnh có cả --prd và --write luôn hiện prompt hỏi bạn trong Claude Code, kể cả khi bật bypass. Lý do: kit thêm hai rule permissions.ask phủ cả hai thứ tự cờ, và rule ask thắng rule allow. Ghi staging và dry-run production chạy không hỏi.
  • Chạy không có --write là dry-run: credential chỉ đọc, lệnh ghi một bản ghi dưới .zenify/data-runs/.
  • --write xin credential ghi và cần một dry-run thành công của đúng lệnh đó (cùng nội dung script) trong 60 phút; bản ghi dùng một lần. Thiếu bản ghi thì lệnh thoát mã 7 trước khi gọi broker và in lệnh dry-run cần chạy trước. Log lần ghi (đã che giá trị) nằm ở docs/scripts/<repo>/<script>.<UTC>.<env>.write.log khi script nằm dưới docs/scripts/<repo>/, không thì ở .zenify/data-runs/.
  • Stdout và stderr được che giá trị và host. Exit code của lệnh con là exit code của zenify.
  • Broker ghi audit mỗi lần xin credential (env, mode, tên biến) và không ghi giá trị. Broker chưa cấu hình secret ghi thì --write báo broker_misconfigured; nhờ maintainer xem Secret ghi và lumi.

Mục broker-creds của zenify doctor xin thử credential staging chế độ đọc và in tên các biến broker cấp (không in giá trị); không có broker thì mục này bỏ qua. Lỗi ở đây nghĩa là zenify data run --stg cũng sẽ lỗi. Khi .zenify/.env còn các key LUMI_MYSQL_URL_* hay QDRANT_URL_* cũ, mục env-file báo không còn dùng, có thể xoá: xoá chúng đi, broker cấp thay.

Tài khoản E2E: e2e login --role ​

bash
zenify e2e login --port 3327 --role agent

Broker cấp một tài khoản E2E riêng cho bạn theo role, trên tenant test của staging. Role hợp lệ: administrator, supervisor, team_leader, agent, sale, sale_team_leader. Lệnh ghi Playwright storage state vào ~/.zenify/playwright/state-<role>.json; không có --role thì dùng administrator và ghi ~/.zenify/playwright/state.json như cũ. Agent znf:ui-verifier nạp file này bằng zenify browser -s=<phiên> state-load <file>. zenify e2e run và zenify visual check cũng nhận --role. Xem Kiểm thử UI.

Grafana, PostHog và Zenie cho agent ​

Khi manifest nhúng sẵn trong binary có broker: và lệnh claude có trên PATH, zenify up --apply đăng ký ba MCP server trong Claude Code ở local scope (chạy từ thư mục gốc workspace, chỉ có hiệu lực trong workspace đó): zenify-grafana (Loki, Prometheus, Tempo), zenify-posthog và zenify-zenie (xem Zenie cho agent). --manifest và file manifest ở thư mục hiện tại không bật bước này. Tên zenify-grafana, zenify-posthog và zenify-zenie dành riêng cho kit: entry local scope kiểu http dưới tên đó có đường dẫn /mcp/grafana, /mcp/posthog hoặc /mcp/zenie bị ghi đè bằng URL broker hiện tại (đây là cách kit cập nhật khi URL broker đổi). Server cùng tên với đường dẫn khác, kiểu stdio, hoặc ở project scope thì kit giữ nguyên. Cả ba dùng type: http tới broker, và Claude Code lấy header đăng nhập bằng zenify broker headers mỗi lần kết nối, nên bạn không điền token nào. Skill /znf:fix dùng chúng ở Step 0 để lấy log, trace và latency khi gặp lỗi trên staging hay production.

Sau khi zenify up --apply, khởi động lại Claude Code để MCP gắn vào phiên.

Zenie cho agent ​

zenify-zenie nối Claude Code với bot Zenie qua broker (/mcp/zenie). Broker gửi kèm GitHub login của bạn, Zenie tra ra người tương ứng trong danh sách của nó; mọi tool đều chỉ đọc. Login chưa gắn với một người đang hoạt động trong Zenie thì mọi lần gọi đều bị từ chối kèm hướng dẫn: nhắn Zenie trên Telegram để gắn GitHub login.

ToolTrả về
review_waitChờ Zenie review xong đúng commit head_sha của một PR rồi trả điểm n/5, check và các finding còn mở (file, dòng, cách sửa). Một lần gọi giữ kết nối tới khi có kết quả (mặc định 600 giây, trần 900), nên agent không phải tự poll sau khi tạo PR hay push. Hết giờ thì trả done: false; gọi lại để chờ tiếp
pr_reviewKết quả review mới nhất của PR, không chờ
pr_review_historyCác lượt review đã xong của PR: commit, điểm, số finding còn mở sau mỗi lượt
item_prsViệc ZEN-n và mọi PR gắn với nó (trạng thái, draft, tác giả, điểm mới nhất); tra theo key hoặc theo repo + pr
item_find, item_read, product_index, product_read, error_list, error_detailChính các tool Zenie dùng khi bạn hỏi nó trên Telegram, chạy dưới danh tính của bạn. Chỉ có khi Zenie đang bật tool đó cho bạn

Finding do model viết từ nội dung PR: agent phải tự kiểm lại với code trước khi sửa. Ghi vào Plane vẫn đi qua zenify-plane (xem dưới); Zenie không nhận lệnh ghi qua MCP.

Plane cho agent ​

zenify-plane là MCP server kiểu stdio: Claude Code chạy zenify plane-mcp bằng PAT Plane của chính bạn, không đi qua broker. Agent đọc work item, cycle, module trực tiếp; mọi thao tác ghi luôn hỏi bạn trước (hook plane-guard); đặt state cho work item qua workitem create/update bị chặn hẳn (Zenie tự chuyển state). Đặt env ZENIFY_PLANE_GUARD=read (không phân biệt hoa thường) thì mọi thao tác ghi đều bị chặn, kể cả khi input hỏng.

Cách bật:

  1. brew install uv.
  2. Chạy zenify up --apply trong terminal của bạn. Khi chưa có PLANE_API_KEY, lệnh mở https://plane.zenify.vn/settings/profile/api-tokens/ trên trình duyệt (và in link đó): bấm Add personal access token, copy token rồi dán vào ô nhập ẩn trong terminal. Lệnh kiểm token với Plane, in tên chủ token, lưu vào <workspace>/.zenify/.env rồi đăng ký zenify-plane luôn. Token bị Plane từ chối thì được dán lại một lần; không gọi được Plane thì lệnh dừng hỏi và nhắc chạy lại sau; Enter bỏ trống để bỏ qua. Lệnh chỉ hỏi khi các bước cài local trước đó đã thành công.
  3. Mở lại Claude Code. /mcp sẽ hiện zenify-plane.

Không có terminal (SessionStart, --non-interactive) thì lệnh không hỏi, chỉ in gợi ý kèm link. Vẫn có thể đặt token bằng zenify env set PLANE_API_KEY.

Chạy zenify up dạng tương tác thì bước nhập secret cuối wizard có thể hỏi PLANE_API_KEY; dán PAT ở đó thì kit đăng ký zenify-plane ngay sau bước đó, không cần chạy lại zenify up --apply. Ô này không kiểm token với Plane; token sai chỉ lộ ra khi /mcp báo lỗi, lúc đó xoá PLANE_API_KEY trong <workspace>/.zenify/.env rồi chạy lại zenify up --apply để dán token mới.

Lần đầu uvx tải Python nên chậm; nếu /mcp báo lỗi kết nối thì mở lại Claude Code. Nếu bước kiểm tra zenify-plane quá thời gian, zenify up in một dòng gợi ý, không đổi gì; chạy lại zenify up --apply sau. Thiếu PAT hoặc uv, zenify up chỉ in gợi ý và không đăng ký. Plane MCP cũng đăng ký ở local scope (như ba MCP qua broker, chỉ có khi mở session ở gốc workspace); hook plane-guard nằm trong .claude/settings.local.json của workspace nên nạp bất kể CLAUDE_CONFIG_DIR.

Tên zenify-plane dành riêng cho kit, chỉ với Type: stdio và Command: zenify; server cùng tên kiểu khác thì kit giữ nguyên. Còn PLANE_API_KEY thì lần zenify up --apply sau sẽ đăng ký lại, nên muốn gỡ hẳn:

  1. Xóa dòng PLANE_API_KEY bằng zenify env edit (zenify env set không nhận giá trị trống), và bỏ PLANE_API_KEY khỏi biến môi trường shell nếu bạn có export.
  2. Chạy claude mcp remove zenify-plane -s local trong thư mục gốc workspace (bản cũ ở user scope: -s user).

Sandbox của Claude Code ​

Khi sandbox Bash của Claude Code bật, lệnh chạy trong sandbox chỉ ra mạng qua proxy lọc của Claude Code, nên script nối thẳng tới MongoDB, MySQL hay Redis không kết nối được. Vì vậy zenify up thêm zenify db-read *, zenify redis-read * và zenify data run * vào cả sandbox.excludedCommands và permissions.allow. Lệnh gọi trần (một lệnh, không pipe, không redirect, không cd, không $(…)) chạy ngoài sandbox, có mạng thẳng và không hỏi quyền. Lệnh ghép, ví dụ có redirect ra file, cd hay $(…), vẫn nằm trong sandbox và không nối được, nên hãy chạy lại dạng trần, với script ghi bằng đường dẫn tuyệt đối.

  • Lệnh con của data run: phần sau -- cũng chạy ngoài sandbox. git-guard vẫn áp luật của nó lên lệnh con. Ghi production vẫn cần role lead ở broker và một lần dry-run trước.
  • Khi nào có hiệu lực: chỉ sau khi chạy zenify up và mở phiên Claude Code mới. Khi sandbox tắt, các mục này không có tác dụng.

Xem thêm ​

zenify broker token, zenify broker headers, zenify redis-read, zenify db-read, zenify data run, zenify e2e login, Đọc dữ liệu thật, Dựng host credential broker