Problem
-
Patterns aren't supported. hookdeck listen 4000 'deepgram-*' fails with a raw API error:
error: name with value deepgram-* fails to match the required pattern: /^[A-z0-9-_]+$/
Only the literal * is special (pkg/listen/source.go#L33). Any other token is sent as GET /sources?name=<token>.
-
A running listen never picks up sources added after it starts, even with '*'. A glob like 'deepgram-*' suggests you'll get events from any matching source that's connected to the CLI, including ones created later. Today you don't: the source list is resolved once, at startup.
Current behavior
Observed with the CLI at 98cc142 and API version 2026-09-01.
-
Resolved once: listen resolves its source argument once at startup. * lists all sources at that moment, and each name is looked up with GET /sources?name=<name>.
-
Session holds connection IDs only: it creates its session with:
POST /cli-sessions
{ "webhook_ids": ["web_a", "web_b"], "filters": { ... } }
That's the connection IDs only, with no record of what was asked for ('*', names or a pattern).
-
Same list on every reconnect: each websocket connect and reconnect sends X-Webhook-Ids: web_a,web_b.
-
New connections aren't routed: a request to a source whose CLI connection isn't in the session's list isn't delivered to the running listen. The request is stored and verified, but gets an ignored event with cause CLI_DISCONNECTED (GET /requests/{id}/ignored_events). That happens even if the new connection uses the same CLI destination as one the session already serves, and even if listen was started with '*'.
To reproduce:
- Create source A and a connection from A to a CLI destination D.
- Run
hookdeck listen 4000 A.
- Create source B and a connection from B to D (or to a new CLI destination E).
- Send a request to B. Nothing reaches
listen, and the request is ignored as CLI_DISCONNECTED.
- Restart
listen (with A,B or '*'), and requests to B are delivered.
Use case
The MCP Events bridge gives local agents a public MCP Events webhook endpoint through Event Gateway and the CLI:
-
Setup: each subscription gets its own MCP_EVENTS source, because the spec makes the webhook secret per subscription and a source holds one secret. Each source has a connection to the agent's CLI destination.
-
The goal: an agent adds subscriptions while running, and we'd like hookdeck listen 4300 'agent-laptop-*' to keep receiving events for new ones.
-
Workaround today: restart listen whenever a source is added, then replay the requests that arrived during the restart:
POST /requests/{id}/retry
{ "webhook_ids": ["<connection id>"] }
Proposal
1. Glob patterns for source names
Support glob patterns, e.g. listen 4000 'deepgram-*' or 'deepgram-*,stripe'.
- Why:
- Projects often group sources by a prefix per service or demo (
deepgram-tts, deepgram-stt).
- A pattern avoids listing every name and the 10-name limit (
pkg/listen/listen.go#L258).
- How:
GET /sources supports name[contains], a case-insensitive substring match. Send the longest literal run as name[contains], then match client-side with path.Match.
- Behavior:
- Treat a pattern as multi-source mode, so
--path is disallowed.
- Error if nothing matches.
- Never auto-create a source from a pattern.
2. Sessions follow newly matching sources
Make the selector part of the session, so new connections that match it are added while listen runs.
-
Session creation carries the selector. The raw source argument and any connection filter are sent alongside the resolved IDs:
POST /cli-sessions
{
"webhook_ids": ["web_a", "web_b"],
"filters": { ... },
"selector": { "sources": ["deepgram-*", "stripe"], "connection": null }
}
-
Matching connections join live sessions. When a connection with a CLI destination is created or updated (POST/PUT /connections), and its source name matches the selector of an active session in the same project:
-
The server adds the connection to that session before responding, so a request sent straight afterwards isn't ignored as CLI_DISCONNECTED.
-
The server pushes a websocket message to the session:
{
"event": "session_updated",
"body": {
"added": [{ "webhook_id": "web_c", "source": { "id": "src_c", "name": "deepgram-tts" }, "cli_path": "/webhooks" }],
"removed": []
}
}
-
removed covers connections that are deleted, disabled or changed so they no longer match.
-
The CLI applies the update:
- It adds the connection to the list it sends as
X-Webhook-Ids on reconnect, so the addition survives reconnects and session expiry.
- It prints e.g.
Now also listening on deepgram-tts → http://localhost:4000/webhooks.
-
Scope: only matching sessions.
- The selector is the opt-in:
'*' matches every source, a pattern matches its sources, and an explicit name matches only that source.
- Other sessions in the project, such as a teammate's
listen on different sources, are unaffected.
-
Compatibility with older CLIs:
- They log unknown websocket message types at debug level and carry on.
- They forward any attempt they receive.
- They resend their original
X-Webhook-Ids on reconnect, so the server should add that list to the session's, not replace it.
Smaller fix, worth doing regardless
Validate source names client-side against ^[A-Za-z0-9_-]+$ and print a helpful error, e.g. Invalid source name "deepgram-*": use letters, digits, - or _ (or '*' for all sources), instead of the raw 422. This also covers typos such as src.a.
Related: #305
Problem
Patterns aren't supported.
hookdeck listen 4000 'deepgram-*'fails with a raw API error:Only the literal
*is special (pkg/listen/source.go#L33). Any other token is sent asGET /sources?name=<token>.A running
listennever picks up sources added after it starts, even with'*'. A glob like'deepgram-*'suggests you'll get events from any matching source that's connected to the CLI, including ones created later. Today you don't: the source list is resolved once, at startup.Current behavior
Observed with the CLI at
98cc142and API version2026-09-01.Resolved once:
listenresolves its source argument once at startup.*lists all sources at that moment, and each name is looked up withGET /sources?name=<name>.Session holds connection IDs only: it creates its session with:
That's the connection IDs only, with no record of what was asked for (
'*', names or a pattern).Same list on every reconnect: each websocket connect and reconnect sends
X-Webhook-Ids: web_a,web_b.New connections aren't routed: a request to a source whose CLI connection isn't in the session's list isn't delivered to the running
listen. The request is stored and verified, but gets an ignored event with causeCLI_DISCONNECTED(GET /requests/{id}/ignored_events). That happens even if the new connection uses the same CLI destination as one the session already serves, and even iflistenwas started with'*'.To reproduce:
hookdeck listen 4000 A.listen, and the request is ignored asCLI_DISCONNECTED.listen(withA,Bor'*'), and requests to B are delivered.Use case
The MCP Events bridge gives local agents a public MCP Events webhook endpoint through Event Gateway and the CLI:
Setup: each subscription gets its own
MCP_EVENTSsource, because the spec makes the webhook secret per subscription and a source holds one secret. Each source has a connection to the agent's CLI destination.The goal: an agent adds subscriptions while running, and we'd like
hookdeck listen 4300 'agent-laptop-*'to keep receiving events for new ones.Workaround today: restart
listenwhenever a source is added, then replay the requests that arrived during the restart:Proposal
1. Glob patterns for source names
Support glob patterns, e.g.
listen 4000 'deepgram-*'or'deepgram-*,stripe'.deepgram-tts,deepgram-stt).pkg/listen/listen.go#L258).GET /sourcessupportsname[contains], a case-insensitive substring match. Send the longest literal run asname[contains], then match client-side withpath.Match.--pathis disallowed.2. Sessions follow newly matching sources
Make the selector part of the session, so new connections that match it are added while
listenruns.Session creation carries the selector. The raw source argument and any connection filter are sent alongside the resolved IDs:
Matching connections join live sessions. When a connection with a CLI destination is created or updated (
POST/PUT /connections), and its source name matches the selector of an active session in the same project:The server adds the connection to that session before responding, so a request sent straight afterwards isn't ignored as
CLI_DISCONNECTED.The server pushes a websocket message to the session:
{ "event": "session_updated", "body": { "added": [{ "webhook_id": "web_c", "source": { "id": "src_c", "name": "deepgram-tts" }, "cli_path": "/webhooks" }], "removed": [] } }removedcovers connections that are deleted, disabled or changed so they no longer match.The CLI applies the update:
X-Webhook-Idson reconnect, so the addition survives reconnects and session expiry.Now also listening on deepgram-tts → http://localhost:4000/webhooks.Scope: only matching sessions.
'*'matches every source, a pattern matches its sources, and an explicit name matches only that source.listenon different sources, are unaffected.Compatibility with older CLIs:
X-Webhook-Idson reconnect, so the server should add that list to the session's, not replace it.Smaller fix, worth doing regardless
Validate source names client-side against
^[A-Za-z0-9_-]+$and print a helpful error, e.g.Invalid source name "deepgram-*": use letters, digits, - or _ (or '*' for all sources), instead of the raw 422. This also covers typos such assrc.a.Related: #305