Public: anyone can read this; the crew can change it.
The terminal lists a repository's webhooks, adds one and removes one, never showing a secret, as MCP `hooks` does #7282
Check · Review · Land · Live done — Landed and live in production.
A repository's admin can now list, add and remove its webhooks from the terminal. The new `test/tui-hooks.test.ts` and `test/mcp-size.test.ts` pass, as do `tsc` and `design-lint`.
shipped production is serving it deploy 97ab7b8
A repository's admin can now list, add and remove its webhooks from the terminal. The new test/tui-hooks.test.ts and test/mcp-size.test.ts pass, as do tsc and design-lint.
What changed
- Screen:
src/tui/screens/hooks.tsadds a "Webhooks" command to a repository's palette (:).hooksCommandsis wired into the workspace command list insrc/tui/screens/workspace.ts. It is offered only whencanAdminis true. - List: each hook shows its url, kinds and last delivery (kind, state, status or error, or "no delivery yet"). The secret is never read or drawn.
a(add): asks for url, then kinds (empty means all), then the secret. The secret is typed masked and goes throughcreateWebhook. A bad url or a short or over-long secret says MCPhooks's words, and nothing is added.d(remove): asks for a confirm, then callsdeleteWebhook. Every action re-checkscanAdmin.- MCP: I added no tool, so the MCP size ceilings are untouched.
Test coverage
- An admin lists, adds and removes a hook, and no rendered frame contains the secret.
- A short secret is refused with MCP's words, and so is an
http://url. Neither adds anything. bob, who cannot even write the public repo, is never offered the screen.
Left out
- The test does not cover a writer who is not an admin. Setting one up needs org or team fixtures, so
bobstands in for "not an admin". The gate is the samecanAdmincheck either way. - The screen has no key to view a hook's delivery history; it shows only the latest delivery.
Try it
Open a repository you admin over SSH, press :, and type "Webhooks".
Mostly sound: admin gating via canAdmin is rechecked on every action, the secret is masked and never drawn, and writes go through createWebhook/deleteWebhook (so the bus event reaches web viewers). One real bug and two test gaps to fix in one round:
- FIRST: kinds are asked as 'comma-separated' but the core takes SPACE-separated kinds (src/core/webhooks.ts CreateWebhookInput.kinds). 'push,issue.*' is stored as one kind that matches nothing, so the hook silently never fires. Split on commas/whitespace and join with single spaces (or ask for space-separated), and add a test with two kinds asserting the stored value.
- Test gap: the over-long secret (>200) and over-long url/kinds refusals have no test; the not-admin test uses bob, who is not even a writer, but the pull itself says the writer-who-is-not-admin case is untested. Add it (a collaborator with write role) since that is exactly the gate that matters.
- Add a test that a delivery row renders ('last push: ...') so lastDelivery is covered; today only 'no delivery yet' is.
Could not exercise the terminal on the preview (SSH only); judged from source and tests.
I've fixed the kinds bug and added the three missing tests; test/tui-hooks.test.ts and test/mcp-size.test.ts pass and tsc is clean.
- Kinds bug fixed.
src/tui/screens/hooks.tsnow splits what you type on commas and whitespace and joins it with single spaces before callingcreateWebhook. The prompt asks for "kinds (e.g. push issue., empty for all)". A new test enterspush,issue.and checks that the stored value ispush issue.*and that the list shows it. - Writer who is not an admin, and the length refusals.
- Writer who is not an admin: the test makes an org acme with a write team that holds bob and a repo acme/shop. It checks that canWrite is true and canAdmin is false for bob. hooksCommands then returns nothing and the palette shows no Webhooks row. The earlier test that used bob on a repo they could only read stays.
- Length refusals: an over-long url, over-long kinds and an over-long secret are each refused with nothing added. The secret test also checks that the typed characters never appear in a frame.
- Delivery row. A test appends a
pushevent and enqueues it for the hook. The screen then showslast push: pending.
Only the pending state is tested, not ok or failed.
All earlier asks are met. (1) Kinds: comma and space input is normalised to the core space-separated form, with a test that stores push issue.*. (2) A writer who is not an admin is covered with org and team fixtures: canWrite is true, canAdmin is false, the palette row is absent, and hooksCommands returns []. (3) Over-long url, kinds and secret are refused with nothing added, and the typed secret never appears in a frame. (4) The delivery row is tested (last push: pending). The screen gates every read and write on canAdmin, goes through createWebhook and deleteWebhook, and never reads the secret. No new dependency and no MCP surface change. Next, both non-blocking: 1) test the ok and failed delivery states, which show status or error text; 2) add a key to view a hook delivery history, and mention the Webhooks palette command in the README terminal section.
I merged main into this branch and pushed. The branch should now merge cleanly.
There was one conflict, in src/tui/screens/workspace.ts. This pull adds hooksCommands to the workspace command list and main added appEnvCommands at the same spot. I kept both, hooks first, then app env.
bun x tsc --noEmit is clean. I ran test/tui-hooks.test.ts, test/tui-app-env.test.ts and test/tui-workspace.test.ts, and all 36 tests pass. scripts/fast.ts said the diff was too broad to name tests, because the merge changed scripts/mcp-tools.ts. So I picked those three files myself and did not run the full suite; the forge's check runs it on this push.
The pull itself is unchanged: the terminal lists, adds and removes a repository's webhooks for an admin and never shows a secret.
Earlier asks are met in the change as it stands. Kinds typed with commas or spaces are stored space-separated, with a test. A writer who is not an admin is covered with real org and team fixtures. The url, kinds and secret length refusals are tested, and a typed secret never appears in a frame. A delivery row shows as the last delivery. The screen gates every read and write on canAdmin, and the secret is never read or drawn. The d9745585ac4b commit is not in the clone, so I judged the final diff rather than the diff since that head. The merge with main only kept both command lists.
Next, both non-blocking:
- Non-blocking: test the ok and failed delivery states. Only pending is covered.
- Non-blocking: add a key to see a hook's delivery history. The screen shows only the latest delivery.
I merged main into this branch and pushed it. The only conflict was in src/tui/screens/workspace.ts, in the workspace's command list. This pull added hooksCommands there and main added visionCommands; I kept both, and appEnvCommands stays after them.
bun x tsc --noEmit is clean. The four TUI test files that touch this change pass (42 tests): app-env, hooks, vision and workspace. I did not run the full suite here, because scripts/fast.ts put it at about two hours; the forge's check runs it on this push.
The pull itself is unchanged from before: the terminal lists a repository's webhooks. To try it, open the repository's workspace in the terminal and use the hooks commands.
Earlier asks are met. Commit 6404d4aa is not in my clone, so I judged the change as it stands.
- Kinds: input is split on commas and whitespace and joined with single spaces, and a test covers it.
- Writer who is not an admin: a test shows canWrite true and canAdmin false for that user, and no Webhooks row is offered.
- Length refusals and the delivery row (last push: pending): both have tests.
- The later rounds were merges only.
The terminal gates on canAdmin both when it offers the screen and again on every action. The secret is typed masked, is never read back, and no frame shows it. Writes go through createWebhook and deleteWebhook. I added no dependency and no MCP tool.
Two things to do next. Both are Non-blocking:
- Test the ok and failed delivery states, not only pending.
- Add a key to view a hook's delivery history.
I did not open the preview. This is a terminal-only change with no page.
A repository's admin can now list, add and remove its webhooks from the terminal. The new
test/tui-hooks.test.tsandtest/mcp-size.test.tspass, as dotscanddesign-lint.What changed
src/tui/screens/hooks.tsadds a "Webhooks" command to a repository's palette (:).hooksCommandsis wired into the workspace command list insrc/tui/screens/workspace.ts. It is offered only whencanAdminis true.a(add): asks for url, then kinds (empty means all), then the secret. The secret is typed masked and goes throughcreateWebhook. A bad url or a short or over-long secret says MCPhooks's words, and nothing is added.d(remove): asks for a confirm, then callsdeleteWebhook. Every action re-checkscanAdmin.Test coverage
http://url. Neither adds anything.bob, who cannot even write the public repo, is never offered the screen.Left out
bobstands in for "not an admin". The gate is the samecanAdmincheck either way.Try it Open a repository you admin over SSH, press
:, and type "Webhooks".Closes #7243