Skip to content

Integrations options

Options under integrations — how the-loop's own calls reach external services.

Control plane only

This governs what the daemon does: posting a reaction, announcing a session, notifying a channel, reading an item's state. What the agent does from inside its session is unconstrained — CLI, MCP, API, whatever the harness has. Nothing here narrows the agent.

Introduced by issue-109 to replace three copies of one setting: ghBinary used to be declared separately under routing.control, routing.reactions and routing.announce. It is now declared once here and fanned out internally. A config still carrying the old key is refused, naming the replacement — see the-loop migrate-config.

yaml
integrations:
  github:
    transport: auto
    cli: { binary: gh }
    api: { tokenEnv: [GITHUB_TOKEN], baseUrl: "" }
  slack:
    transport: sdk
    urlEnv: THE_LOOP_SLACK_WEBHOOK_URL
    # url: https://hooks.slack.com/services/…   # takes precedence; commits the credential

Choosing a transport

Transport is a choice, not a mandate:

  • auto resolves token → binary, and fails closed naming both remedies if neither is available. It never guesses silently.
  • An explicit transport is honoured verbatim and fails rather than degrading. If you asked for api and the token is missing, you get an error — not a quiet fallback to a CLI that might be authenticated as somebody else.

GitHub

github.transport

  • Type: 'auto' | 'api' | 'cli'
  • Default: auto

How GitHub calls are made:

ValueMeans
apistdlib HTTP with a token from github.api.tokenEnv
clithe operator's authenticated gh, inheriting enterprise and SSO settings
autotoken first, then binary; fails closed naming both remedies

github.api.tokenEnv

  • Type: string[]
  • Default: none

Environment variables holding a token, tried in order.

Variable names, never tokens

This is a list of variable names. Putting a token in this file commits it.

github.api.baseUrl

  • Type: string
  • Default: none (github.com)

API base URL — set it for a GitHub Enterprise host.

github.cli.binary

  • Type: string
  • Default: gh

Path or name of the gh CLI. One declaration, used by every feature that shells out to GitHub: control-command paper-trail comments, dispatch reactions, session announcements, and the GitHub poll provider.

This is the key that replaced the three ghBinary declarations.

Slack

slack.transport

  • Type: 'auto' | 'sdk' | 'webhook'

  • Default: sdk

  • sdk — the official slack-sdk, a required dependency since the extras were removed (owner decision, PR #162), so it is always present.

  • webhook — a raw POST to an incoming-webhook URL, using nothing but the standard library.

slack.urlEnv

  • Type: string
  • Default: THE_LOOP_SLACK_WEBHOOK_URL

Environment variable holding the incoming-webhook URL. The default source, and the one to keep where this file is shared or public.

It is a credential

A Slack incoming-webhook URL is the credential — anyone holding it can post to your channel. Keeping it in the environment keeps it out of git.

slack.url

  • Type: string
  • Default: none — urlEnv is read instead

The incoming-webhook URL itself, for an operator who has judged it non-secret (a personal channel in a private workspace, say). Takes precedence over urlEnv, so the effective configuration never depends on ambient environment: reading this file tells you where a notification goes.

yaml
integrations:
  slack:
    transport: sdk
    url: https://hooks.slack.com/services/XXX/YYY/ZZZ

Setting this commits the credential

A webhook URL is a bearer credential — for one channel, with no read access and no workspace scope, but a credential. Put it here and it lands in git history, where deleting the line does not remove it; disclosure is fixed by deleting the webhook in Slack, not by editing the file. Prefer urlEnv unless you have decided otherwise on purpose.

The reason the key exists is the failure mode it removes. With env-only configuration, the variable has to be present in every process that might deliver a notification — the poll daemon (which inherits it from whichever shell ran poll start), the harness sessions that daemon spawns, and every fresh machine's provisioning. Restart the daemon from cron, systemd or a new SSH session without the export and notifications stop with nothing louder than a log line, because notify is best-effort by contract. An empty url: is treated as absent and falls back to urlEnv, so a blank key cannot silently disable a working setup.

When neither source resolves, the error names both:

text
slack has no webhook url — set integrations.slack.url in the CLI config,
or export THE_LOOP_SLACK_WEBHOOK_URL

Jira

jira.transport

  • Type: 'auto' | 'api' | 'cli'
  • Default: api

How Jira calls are made. Same semantics as GitHub's.

jira.api.baseUrl

  • Type: string
  • Default: none

Jira API base URL, e.g. https://your-org.atlassian.net.

jira.api.tokenEnv

  • Type: string
  • Default: none

Environment variable holding the Jira API token. A name, not a token.

jira.cli.binary

  • Type: string
  • Default: jira

Path or name of the Jira CLI.

Next

Released under the MIT License.