Appearance
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
Đăng nhập GitHub CLI với scope
read:org.bashgh 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.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áo | Việ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ại | Mở 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,countkhông có--filter(tổng số ước lượng quaestimatedDocumentCount;countcó--filterchỉ dành cho lead),explainở mứcqueryPlanner(chỉ in plan, không chạy truy vấn nên không có executionStats),indexes,stats,tables, vàsqldạngEXPLAIN [FORMAT=…] SELECT …(đúng mộtSELECT, không subquery, khôngANALYZE, khôngWITH),DESCRIBE/DESC <bảng>,SHOW INDEX/SHOW INDEXES/SHOW KEYS,SHOW CREATE TABLE,SHOW TABLE STATUS,SHOW TABLES,SHOW COLUMNS. CâuSHOW/DESCRIBEchỉ gồm từ khoá, tên bảng hoặc database vàLIKE '<chuỗi>'; cóWHERE,(haySELECTthì 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ủamongosh/mysqlcũ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:
EXPLAINcủ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ọigh auth tokennhư 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:agentkhiGH_TOKENbắt đầu bằngghs_, còn lạiuser); 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 headerX-Zenify-Taskở mọi request tới broker và broker ghi vào fieldtaskcủ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_roomsCờ --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. SCANtự thêmCOUNT 100nếu thiếu;COUNTphải trong khoảng 1 đến 1000.- Broker giới hạn mỗi lần đọc:
LRANGE,ZRANGE,XRANGEtối đa 200 phần tử; kết quả tối đa 1 MiB:GETvà các lệnh khác bị cắt và lệnh ghi thông báo ra stderr, cònMGET,HGET,HGETALL,SMEMBERSvượt mức thì bị từ chối;MGETtối đa 100 key;HGETALL,HKEYS,SMEMBERStừ chối key có hơn 10000 phần tử. - Cờ (
--prd) phải đứng trước lệnh Redis, để tham số như-1khô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--prdsẽ thành tham số Redis và lệnh chạy nhầm trên staging. - Kết quả
LRANGE,ZRANGE,XRANGEbị cắt ở 200 phần tử thì lệnh inzenify redis-read: kết quả đã bị cắt bớtra 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.jszenify 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
--stghoặc--prd; không thì thoát mã 2.--prdchỉ dành cho lead. - Lệnh có cả
--prdvà--writeluôn hiện prompt hỏi bạn trong Claude Code, kể cả khi bật bypass. Lý do: kit thêm hai rulepermissions.askphủ 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ó
--writelà dry-run: credential chỉ đọc, lệnh ghi một bản ghi dưới.zenify/data-runs/. --writexin 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.logkhi script nằm dướidocs/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ì
--writebáobroker_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 agentBroker 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.
| Tool | Trả về |
|---|---|
review_wait | Chờ 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_review | Kết quả review mới nhất của PR, không chờ |
pr_review_history | Các lượt review đã xong của PR: commit, điểm, số finding còn mở sau mỗi lượt |
item_prs | Việ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_detail | Chí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:
brew install uv.- Chạy
zenify up --applytrong 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/.envrồi đăng kýzenify-planeluô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. - Mở lại Claude Code.
/mcpsẽ hiệnzenify-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:
- Xóa dòng
PLANE_API_KEYbằngzenify env edit(zenify env setkhông nhận giá trị trống), và bỏPLANE_API_KEYkhỏi biến môi trường shell nếu bạn có export. - Chạy
claude mcp remove zenify-plane -s localtrong 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 upvà 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