fix(webhook): bind a subscription to a profile with --route-profile, not --profile
The salvaged flag was spelled --profile, which collides with the global -p/--profile that hermes_cli.main scans BEFORE argparse: `hermes webhook subscribe x --profile compta` would switch this CLI process to compta's HERMES_HOME and write the subscription into compta's webhook_subscriptions.json — a file the default gateway's webhook adapter never reads — while the route still lacked the profile key. #109020 special-cased the scanner for the webhook subcommand; naming the flag --route-profile removes the ambiguity without touching _scan_profile_flag: -p picks the gateway whose subscriptions file is written, --route-profile picks which /p/<profile>/ prefix may hit the route. Docs: cli-commands reference row, multi-profile-gateways webhook section, the route `profile` field. Builds on #109020 (fangliquanflq). Fixes #109016.
This commit is contained in:
@@ -27,8 +27,11 @@ def build_webhook_parser(subparsers, *, cmd_webhook: Callable) -> None:
|
||||
"--deliver-chat-id", default="", help="Target chat ID for cross-platform delivery")
|
||||
wh_sub.add_argument("--secret", default="", help="HMAC secret (auto-generated if omitted)")
|
||||
wh_sub.add_argument(
|
||||
"--profile", default=None,
|
||||
help="Profile that may receive this route (default: default; preserved on update)")
|
||||
"--route-profile", dest="route_profile", default=None, metavar="PROFILE",
|
||||
help="Bind the route to a multiplexed profile: only POSTs to /p/PROFILE/webhooks/<name> "
|
||||
"are accepted and the agent runs as that profile (default: default; kept on update). "
|
||||
"Distinct from the global -p/--profile, which picks the gateway whose subscriptions "
|
||||
"file is written.")
|
||||
wh_sub.add_argument(
|
||||
"--deliver-only", action="store_true",
|
||||
help="Skip the agent — deliver the rendered prompt directly as the "
|
||||
|
||||
@@ -119,7 +119,7 @@ def _cmd_subscribe(args):
|
||||
subs = _load_subscriptions()
|
||||
is_update = name in subs
|
||||
existing = subs.get(name, {})
|
||||
profile_arg = getattr(args, "profile", None)
|
||||
profile_arg = getattr(args, "route_profile", None)
|
||||
if profile_arg is None:
|
||||
profile = existing.get("profile", "default")
|
||||
else:
|
||||
|
||||
@@ -35,7 +35,7 @@ def _make_args(**kwargs):
|
||||
"deliver": "log",
|
||||
"deliver_chat_id": "",
|
||||
"secret": "",
|
||||
"profile": None,
|
||||
"route_profile": None,
|
||||
"payload": "",
|
||||
"script": "",
|
||||
}
|
||||
@@ -72,7 +72,7 @@ class TestSubscribe:
|
||||
profile_dir.mkdir(parents=True)
|
||||
|
||||
webhook_command(_make_args(
|
||||
webhook_action="subscribe", name="notifier", profile="compta"
|
||||
webhook_action="subscribe", name="notifier", route_profile="compta"
|
||||
))
|
||||
created = _load_subscriptions()["notifier"]
|
||||
first_secret = created["secret"]
|
||||
@@ -91,7 +91,7 @@ class TestSubscribe:
|
||||
webhook_action="subscribe", name="notifier", secret="original"
|
||||
))
|
||||
webhook_command(_make_args(
|
||||
webhook_action="subscribe", name="notifier", profile="missing"
|
||||
webhook_action="subscribe", name="notifier", route_profile="missing"
|
||||
))
|
||||
|
||||
assert "does not exist" in capsys.readouterr().out
|
||||
|
||||
@@ -841,8 +841,9 @@ hermes webhook subscribe <name> [options]
|
||||
| `--secret` | Custom HMAC secret. Auto-generated if omitted. |
|
||||
| `--deliver-only` | Skip the agent — deliver the rendered `--prompt` as the literal message. Zero LLM cost, sub-second delivery. Requires `--deliver` to be a real target (not `log`). |
|
||||
| `--script` | Filter/transform script under `~/.hermes/scripts/`. The webhook payload is passed as JSON on stdin; JSON stdout replaces the payload, and empty stdout, `[SILENT]`, or a nonzero exit code ignores the webhook. See [Script Filters and Transforms](../user-guide/messaging/webhooks.md#script-filters-and-transforms). |
|
||||
| `--route-profile` | Bind the route to a multiplexed profile: it is then reachable only at `/p/<profile>/webhooks/<name>` and the agent runs as that profile. Validated against existing profiles; kept on update when omitted. Not the same as the global `-p/--profile`, which selects the gateway whose subscriptions file is written. See [Multi-profile gateways](../user-guide/multi-profile-gateways.md). |
|
||||
|
||||
Subscriptions persist to `~/.hermes/webhook_subscriptions.json` and are hot-reloaded by the webhook adapter without a gateway restart.
|
||||
Subscriptions persist to `~/.hermes/webhook_subscriptions.json` and are hot-reloaded by the webhook adapter without a gateway restart. Re-running `subscribe` for an existing name keeps its secret and profile binding unless you pass `--secret` / `--route-profile`.
|
||||
|
||||
## `hermes doctor`
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ Routes define how different webhook sources are handled. Each route is a named e
|
||||
|----------|----------|-------------|
|
||||
| `events` | No | List of event types to accept (e.g. `["pull_request"]`). If empty, all events are accepted. Event type is read from `X-GitHub-Event`, `X-GitLab-Event`, or `event_type` in the payload. |
|
||||
| `secret` | **Yes** | HMAC secret for signature validation. Falls back to the global `secret` if not set on the route. Set to `"INSECURE_NO_AUTH"` for testing only (skips validation). |
|
||||
| `profile` | No | Profile authorized to execute this route when `gateway.multiplex_profiles` is enabled. Omit it for a default-profile-only route; set a profile name (for example `coder`) to bind the route and its secret to `/p/coder/webhooks/<route>`. |
|
||||
| `profile` | No | Profile authorized to execute this route when `gateway.multiplex_profiles` is enabled. Omit it for a default-profile-only route; set a profile name (for example `coder`) to bind the route and its secret to `/p/coder/webhooks/<route>`. Dynamic subscriptions set it with `hermes webhook subscribe <name> --route-profile coder`. |
|
||||
| `prompt` | No | Template string with dot-notation payload access (e.g. `{pull_request.title}`). If omitted, the full JSON payload is dumped into the prompt. Payload fields are untrusted — see [Authenticated does not mean trusted](#authenticated-does-not-mean-trusted). |
|
||||
| `filters` | No | Declarative payload filters evaluated after auth/body/event filtering and before agent or direct delivery work. Non-matches return `{"status":"ignored","reason":"filter"}` with HTTP 200. |
|
||||
| `script` | No | Filter/transform script under `~/.hermes/scripts/`. The webhook payload is passed as JSON on stdin. JSON object stdout replaces the payload before templating; text stdout is exposed as `script_output`; empty stdout, `[SILENT]`, or a nonzero exit code ignores the webhook. |
|
||||
|
||||
@@ -193,7 +193,13 @@ using the default listener's existing credentials.
|
||||
`config.yaml`. That secret is then accepted only at
|
||||
`/p/coder/webhooks/<route>` and is rejected on every other profile prefix.
|
||||
- Webhook routes without `profile` remain default-profile routes and are not
|
||||
reachable through a named profile prefix.
|
||||
reachable through a named profile prefix. Dynamic subscriptions bind the same
|
||||
way: `hermes webhook subscribe <name> --route-profile coder` writes
|
||||
`profile: coder` into the default gateway's `webhook_subscriptions.json` and
|
||||
prints the `/p/coder/webhooks/<name>` URL (`hermes webhook ls` shows the
|
||||
binding). Use `--route-profile`, not the global `-p coder`: `-p` would write
|
||||
the subscription into coder's own subscriptions file, which the default
|
||||
gateway's webhook adapter never reads.
|
||||
- Delivery follows the same binding. A `profile: coder` route's reply (or
|
||||
`deliver_only` message) goes out through **coder's** adapter for the
|
||||
`deliver` platform, falls back to **coder's** home channel when
|
||||
|
||||
Reference in New Issue
Block a user