Triggers and replies
Start an agent run from a Linear issue, Slack mention, Sentry issue or Figma comment, and post the result back.
A connected tool can start work in Dock. The tool sends a webhook to the control plane, the control plane checks its signature, finds the workspace, and starts a run: a dock with that connector attached, running your agent on a prompt built from the event. When the agent finishes, Dock posts its summary back where the event came from.
| Kind | Starts a run when | Dock posts back |
|---|---|---|
linear | An issue is assigned to the connected Linear user, or the label dock is added | A comment; the issue moves to a started state when the run begins and to a completed state when the agent reports done |
slack | The app is mentioned (app_mention) | A message in the mention's thread (thread_ts), plus an "On it." acknowledgement |
sentry | A new issue (issue.created) or an issue alert (event_alert) fires | A note on the issue |
figma | A file comment mentions the connected Figma user or writes @dock | A reply in the comment thread, plus an "On it." acknowledgement |
A Sentry issue that creates an issue webhook and an alert webhook starts one run:
a subject (issue, Slack thread, Figma thread) has at most one run in flight. A
workspace runs at most CONNECTOR_RUN_MAX_CONCURRENT runs at once (default 5);
further events are not queued: Slack, Linear and Figma users get a "busy, try
again" reply and Sentry events are dropped without a note.
Who can start a run
A run is an agent acting with the connected tool's credential, so Dock checks who sent the event before anything starts. A refused event starts no dock and spends no model usage. Where the tool allows, Dock replies with a short reason; the check fails closed, so an event it cannot verify is ignored silently.
| Tool | Who may start a run |
|---|---|
| Slack | Full members of the connected Slack workspace. Guests (is_restricted, is_ultra_restricted), bots, deactivated users and Slack Connect users from other teams are refused. Dock asks Slack (users.info) with the stored token, so the token needs users:read. |
| Linear | Active, non-guest members of the connected organization, checked with the stored token. Events caused by integrations or OAuth apps are ignored. |
| Figma | Figma users on the connector's allowlist (allowedUsers). By default the list holds only the Figma user of the token that was connected. Add colleagues with allowedUsers in the webhook configuration. When a different Figma user reconnects, the list is reset to that new user; the same user reconnecting keeps the list. |
| Sentry | Nobody until an admin sets triggersEnabled: true in the webhook configuration. Event text can contain text from end users of the monitored app (exception messages, breadcrumbs), so Sentry triggers are opt-in. |
Security model
Anyone who can start a run can steer an agent with prompt injection, so a run is built to hold nothing worth stealing and to be unable to hand it back:
- No environment secrets. The dock is created without the workspace
environment: none of its variables, secret files, repositories, provider keys
or dock API key are applied. The agent installs from the image or from the
operator's
CONNECTOR_RUN_SETUP_SCRIPT, which must not contain secrets. - No provider key. The agent reaches its model through the LLM gateway with a
run-scoped token (
DOCK_LLM_TOKEN), exactly as GitHub runs do. The key stays in the control plane, and the token stops working when the run completes, fails or times out. - A scoped, revocable tool token. The dock holds
DOCK_CONNECTOR_TOKEN, which works only through the connector gateway and only while the dock runs. Dock stops the dock when the run ends, which revokes it. - Scrubbed replies. Every reply, including summaries and failure reasons, passes
through a redaction step before it is posted: the dock's own secret values, then
common credential shapes (Sentry, Slack, Linear and Figma tokens,
sk-andsk-ant-keys, GitHub tokens, AWS keys, JWTs, bearer headers, Dock tokens and other long hex strings). Replies are capped at 2000 characters and cannot contain@dock, so a reply never triggers another run. - Admins own the run. The dock is created as the admin who connected the tool, so the admins-only rule for docks with connectors holds. Reconnecting the tool makes the reconnecting admin the owner.
What remains: the agent can still use the connected tool with that credential's permissions while it runs (for example read every Linear issue), and it can send data it can see over the network. Connect tools with the least access that works, keep the sender checks above tight, and treat a run like a teammate with that tool's access.
Open pull requests in a linked repository
By default a triggered run can investigate and reply, and nothing more. An admin can link a connector scope to a GitHub repository so the run can change code and open a pull request there.
| Tool | Scope key |
|---|---|
linear | Team id |
slack | Channel id |
sentry | Project slug |
figma | File key |
Use * as the scope to link every subject of that tool. A run uses the link for
its own scope first and falls back to *. Without a matching link nothing
changes: the run replies only.
GET /v1/workspaces/:workspaceId/connectors/:kind/repo-linkslists the links.PUT /v1/workspaces/:workspaceId/connectors/:kind/repo-linkscreates or replaces one with{ "scope": "<scope key or *>", "repo": "owner/name" }.DELETE /v1/workspaces/:workspaceId/connectors/:kind/repo-links?scope=<scope key>removes one.
All three are for organization admins only, and the tool must be connected.
Linking checks that a GitHub App installation of the
workspace lists the repository and that the App has Contents and Pull requests
write access on it: 400 github_repo_not_accessible,
400 github_permissions_missing, 400 github_installation_missing or
503 github_app_not_configured otherwise.
curl -X PUT $DOCK_API/v1/workspaces/$WORKSPACE_ID/connectors/linear/repo-links \
-H "Authorization: Bearer $DOCK_API_KEY" -H "content-type: application/json" \
-d '{"scope":"<linear team id>","repo":"acme/app"}'For a linked run:
- Dock pins the repository's default branch head and mints an installation token
of the Dock GitHub App limited to that one repository with
contents: read. The token reaches only the one exec that clones, through a git credential helper, so it is in no URL, file or git config; Dock revokes it as soon as the clone ends. - The agent works on branch
dock/<tool>-<short run id>in the clone. It holds no GitHub credential and cannot push. - When the agent reports
done, the dock returns the changed files. The control plane mints a second token limited to the same repository withcontents: writeandpull_requests: write, commits the changes on the branch from the pinned base commit, opens a draft pull request, and revokes the token. The title comes from the subject; the body links back to the Linear issue, Slack thread, Sentry issue or Figma file, and carries the run summary after the same redaction as replies (GitHub tokens included).@mentions in it are neutralized. - The reply in the tool includes the pull request link. A Linear issue is not
moved to completed when a pull request was opened. If the pull request cannot
be opened the reply says so with a short reason (
push_failed,pr_create_failed,token_mint_failed,workflow_change_refusedfor a change under.github/workflows/,repo_unavailable).
A change set too large (over 1000 files or 8 MiB) or touching a symlink target
Dock cannot represent fails the run. A run that cannot clone fails with
repo_unavailable, as does a run whose link points at a GitHub App installation
no longer linked to the workspace: removing an installation stops its repository
links from being used.
Who can cause a pull request is exactly who can start a run, as set out in Who can start a run; nothing about the link widens it. Anyone allowed to start a run can steer the agent, so treat a link like giving those people a contributor who opens draft pull requests in that repository. Pull requests are authored by the Dock GitHub App, never by a person, never merged by Dock, and always opened as drafts for human review. The run still has no environment secrets, and no GitHub token is ever placed in the agent's environment.
What a link does give those people:
- Read access to the repository. The agent has the whole clone and the dock has network egress, so a requester can have it send the code elsewhere. Do not link a repository the people who can start runs should not read.
- Code in your CI. The branch is pushed to the repository itself, not a fork,
and pushes made by a GitHub App start workflows.
pushandpull_requestworkflows then run the agent's version of the code (build scripts, tests, dependency manifests) with the repository's Actions secrets. Changes under.github/workflows/are refused, but that does not stop a changed test script. Before linking, keep secrets out of workflows that run ondock/**branches, for example with deployment environments that require reviewers.
How a run works
The run acts on behalf of the organization admin who connected the tool
(created_by on the connector), so the admins-only rule for docks with connectors
holds. See Security model for what the dock holds.
- The control plane picks a model the organization's provider keys can serve,
then creates a dock in the workspace with the connector attached and no
environment. Dock never installs agents: the image, or
CONNECTOR_RUN_SETUP_SCRIPT, must provide the agent named byCONNECTOR_RUN_AGENT(opencodeby default, orcodex), and the organization must have a provider key the agent can use through the gateway (otherwise the run fails withprovider_not_configured). - The agent gets a prompt built from the event (sanitized and bounded like a
GitHub issue) and the tool's API at
$DOCK_<KIND>_API_URLwith$DOCK_CONNECTOR_TOKEN. It writesRESULT.jsonwith astatusofdone,no_changeorfailedand asummary. - The dock calls back with
POST /v1/connectors/runs/:id/complete, authorized by a token only that dock holds; the first result wins. Dock then posts the summary and stops the dock. - A run that never reports is failed after
CONNECTOR_RUN_TIMEOUT_MINUTESplus a 15 minute margin (default 30), with a reply naming the reason (timed_out,launch_failed,dock_provision_failed,provider_not_configured,agent_unsupported,no_owner,repo_unavailable). A completion and a timeout cannot both win: the first one to settle the run is the only one that replies.
Configure verification
Each tool signs its webhooks differently. Dock finds the workspace from a vendor account id stored at connect time or by you, and verifies the signature with a secret stored sealed on the connector or, for Linear and Slack, an app-level secret from the environment.
PUT /v1/workspaces/:workspaceId/connectors/:kind/webhook (admins only) records
the secret and routing id for a connected tool:
curl -X PUT $DOCK_API/v1/workspaces/$WORKSPACE_ID/connectors/sentry/webhook \
-H "Authorization: Bearer $DOCK_API_KEY" -H "content-type: application/json" \
-d '{"secret":"<client secret>","externalId":"<installation uuid>"}'| Field | Meaning |
|---|---|
secret | The signing secret or passcode; 8 to 512 characters. Stored sealed, never returned. Omit it for Figma to have Dock generate a passcode and return it once. |
allowedUsers | Figma user ids that may start runs (up to 100). Replaces the list. |
triggersEnabled | true lets Sentry events start runs. Off by default. |
externalId | The routing id: Sentry installation uuid or Figma webhook id. Linear (organization) and Slack (team) ids are recorded automatically when the token is connected; a different Linear or Slack id is accepted only with your own secret, since the app-level secret cannot vouch for it. |
400 webhook_secret_required means no secret is stored and no app-level secret
covers the kind. 409 connector_account_in_use means another workspace already
holds that vendor account.
An unknown account answers 404 connector_not_found; a wrong signature answers
401 invalid_signature; a stale Linear or Slack timestamp answers
401 stale_webhook. A delivery seen before is acknowledged with
duplicate: true and starts nothing. Deliveries are deduplicated per tool by
Linear's Linear-Delivery, Slack's event_id, Sentry's Request-ID and, for
Figma, webhook id, event type, comment id and timestamp. Linear and Sentry do not
sign those headers, so a repeat of the same signed body is a duplicate too.
Inbound routes
These routes are called by the tools, not by clients. Each answers 200 at once
and processes the event in the background (Slack requires an answer within three
seconds).
Linear
POST /v1/connectors/linear/webhooks. Verified with Linear-Signature: the hex
HMAC-SHA256 of the raw body with the webhook's signing secret, and the
webhookTimestamp in the body must be within one minute. Routed by
organizationId.
Slack
POST /v1/connectors/slack/events. Verified with X-Slack-Signature
(v0= plus the hex HMAC-SHA256 of v0:{timestamp}:{raw body} with the signing
secret); requests whose X-Slack-Request-Timestamp is more than five minutes old
are refused. url_verification is answered with the challenge. Routed by
team_id. Messages from bots are ignored.
Sentry
POST /v1/connectors/sentry/webhooks. Verified with Sentry-Hook-Signature: the
hex HMAC-SHA256 of the request body with the integration's client secret. Sentry's
timestamp header is not signed, so replays are stopped by deduplicating the signed
body. Routed
by installation.uuid; the resource comes from Sentry-Hook-Resource.
Figma
POST /v1/connectors/figma/webhooks. Webhooks v2 put the passcode chosen at
creation in every payload; Dock compares it in constant time. Routed by
webhook_id. The PING sent when a webhook is created is acknowledged. Only
allowlisted commenters start runs; replies never contain @dock, so they do not
loop.
Run completion
POST /v1/connectors/runs/:id/complete is used only by the run's dock, with
Authorization: Bearer <run token> and { "status": "done" | "no_change" | "failed", "summary"?, "reason"? }.
Set up each tool
| Linear | Slack | Sentry | Figma | |
|---|---|---|---|---|
| Webhook URL | <PUBLIC_API_URL>/v1/connectors/linear/webhooks | <PUBLIC_API_URL>/v1/connectors/slack/events | <PUBLIC_API_URL>/v1/connectors/sentry/webhooks | <PUBLIC_API_URL>/v1/connectors/figma/webhooks |
| Subscribe to | Issues | Bot event app_mention | issue (created) and, for alerts, an alert rule action that calls the integration | FILE_COMMENT |
| Secret | Webhook signing secret, or LINEAR_WEBHOOK_SECRET | Signing secret, or SLACK_SIGNING_SECRET | The integration's client secret, sent in secret | The passcode (generated by Dock when omitted) |
| Routing id | Automatic | Automatic | Installation uuid, sent in externalId | Webhook id from the create response, sent in externalId |
| Scopes the reply needs | write | chat:write, app_mentions:read, users:read | event:write (user token) | file_comments:write |
| Who may start a run | Non-guest members | Full members of the team | Opt in with triggersEnabled | Allowlisted Figma users |
Tokens connected before these triggers existed may lack the reply scopes: reconnect with a token that has them.
Environment
| Variable | Meaning |
|---|---|
LINEAR_WEBHOOK_SECRET | App-level Linear signing secret, used when the connector stores none |
SLACK_SIGNING_SECRET | App-level Slack signing secret, used when the connector stores none |
CONNECTOR_RUN_AGENT | opencode (default) or codex; must be in the image or installed by the setup script |
CONNECTOR_RUN_MODEL | provider/model served through the LLM gateway; empty picks one from the organization's provider keys |
CONNECTOR_RUN_SETUP_SCRIPT | Non-secret script that installs the agent in connector docks, which receive no environment |
CONNECTOR_RUN_MAX_CONCURRENT | Runs per workspace at once (default 5) |
CONNECTOR_RUN_TIMEOUT_MINUTES | Agent budget in minutes, at least 5 (default 30) |
Sentry and Figma have no app-level secret on purpose: their routing ids are typed by an admin, so only a secret the admin holds may vouch for them.