Installed integrations: Linear, Jira, Bitbucket, PostHog and operator tools
Besides GitHub, Open Conduit ships adapters for Linear and Jira (trackers), Bitbucket Cloud (a code host for a tracker scope) and PostHog (telemetry), plus operator tools that work across every integration. The trackers share the kit in extensions/tracker; each adapter is its own extension under extensions/, and internal/bootstrap composes them. The tests run them against fakes (lineartest, jiratest, bitbuckettest and a PostHog capture fake); they have not been checked against real accounts (see Limitations).
The shared tracker kit
Section titled “The shared tracker kit”extensions/tracker is the code every non-GitHub tracker shares. It imports only public contracts. A tracker supplies a Service (its API client and webhook format), and the kit supplies:
- Ingress. Push and reconcile share one normalizer, so equal state has an equal delivery ID from either transport.
- Families are
<type>.issue.open|closed,<type>.comment.human|bot|agentand the one-shot<type>.delivery. - An issue is keyed
issue/KEY, orissue/KEY/generation/reopen-<unix>after a reopening. The generation is derived from the service’s immutable reopen history. A comment joins the generation current when it was written. - The issue payload omits update times, status labels and any workflow status the entry’s
transitionsmanage, so projection never feeds back as input. - Reconcile lists open issues and issues updated within
closed_horizon_hoursin creation order, plus each listed issue’s comments. Pages are bounded by detail reads and wall time. Failures are errors, so no absence is inferred. - Push re-reads exactly the issue or comment a verified delivery reports.
- Families are
- Route (handler = type). The opt-in/skip claim policy:
- The issue must be open and not waiting: outside Linear’s claim states.
- It must have no skip label, and a required label when any are configured.
- It must not carry the claimed label, unless it is a reopened generation.
- An optional route
matchon attributes (labels, status, title, team, project, priority, assignee, components, custom fields) selects issues per route. - Input: closes, human comments and other Works’ agent comments; never edits, bot comments or the Work’s own notes.
- An operator’s
<type>.claim.requestedoverrides the policy. For GitHub this is the entry’sclaimaction, accepted undercore.action.
- Projector. It is level-triggered from durable Work state:
- Labels: claimed while owned, plus at most one of the help, review or done labels, none while an attempt runs. The claimed label comes off once completed Work’s issue is closed: admission never claims a closed issue, and a reopening starts a new generation, which is claimed again. Failed and cancelled Work keep it.
- It re-reads the issue when desired state changes, and at
verify_secondsfor nonterminal Work and for completed Work whose issue is still open, until the issue closes. After it moves a completed Work’s issue (afinishedtransition that may close it), the next pass re-reads it at once. - Workflow status: claimed, then the declared status (
core.turn.v1{status, comment}), done or help for completed or failed Work. It is moved only when the entry names a status for that state. - Each outcome comment goes through the
Outbox: pending → attempted (recorded before the write) → posted, with mark search after a lost answer, so nothing is posted twice across a crash.
- Tools.
<type>.context,.issue.view,.issue.comment(a durable intent, idempotent per request),.issue.listand.issue.create(never repeated after an ambiguous answer).
The runtime holds no tracker vocabulary. Labels and transitions exist only in the kit’s projector, and the same SQLite runtime cases run for both trackers (TestTrackerReconcileClaimProjectAndRecover).
Boundary decision (provisional). extensions/linear, extensions/jira and extensions/bitbucket may import the public-only kit. No other extension may, and the kit reaches no extension. The boundary checker and its cases enforce this.
Linear
Section titled “Linear”extensions/linear works one or more teams over the GraphQL API.
- Connection.
api_urlandagent_account. The secret istoken: a personal key, orlin_oauth_…sent as a bearer token. - Entry.
teamsandstates(claim-from state types or names; default unstarted, started), plus the kit’s fields. - Rate limits and credentials. A
RATELIMITEDanswer pauses the client untilX-RateLimit-Requests-Reset. A 401 re-resolves the token once. - Marks. Linear drops HTML comments, so a comment carries a visible line
*open-conduit work=… intent=…*. The line is visible in Linear: a provisional decision, not yet checked against how Linear renders it. - Webhooks.
Linear-Signature(hex HMAC-SHA256), a ten-minute replay window on the signedwebhookTimestamp(a delivery without one, or dated more than ten minutes ahead, is refused) andLinear-Deliveryas the identity.manage_webhookcreates a team’s hook, or an all-public-teams hook for several teams. That needs a workspace admin. - Labels. A missing managed label is created on first use.
extensions/jira works Cloud or Data Center projects over REST API v2.
- Connection.
base_url,auth(basic withemail, or bearer) andagent_account. The secret istoken. - Entry.
projects,jqlandfields(custom fields routes can match), plus the kit’s fields. Labels cannot contain spaces. - Bodies. Wiki markup is converted to and from Markdown.
- Listing. The enhanced search (
/search/jql, token pages) falls back to the classic search on a 404. - Closed. An issue is closed once its status is in the done category or, when the issue carries no category, once it has a resolution. A
finishedordonetransition to a done-category status therefore closes it, and completed Work then loses the claimed label. - Reopens. They come from the changelog against status categories, or from a cleared resolution when the categories are unreadable.
- Marks. Comments carry an entity property (
workandintent). - Transitions. Matched by target status first, then by transition name.
- Webhooks.
X-Hub-SignatureHMAC through the admin API (needs an administrator, refusal codehook_permission_denied). Intentional difference: unsigned deliveries are refused rather than accepted with a log line; reconcile recovers what they would have said.
Bitbucket Cloud
Section titled “Bitbucket Cloud”extensions/bitbucket is the code host for a tracker scope.
- Families.
bitbucket.pull_request.open|closed|merged,bitbucket.comment.*,bitbucket.checks.pending|passed|failed(commit statuses, with the preview URL frompreview_pattern) and the one-shotbitbucket.delivery. - Correlation. A PR is keyed to its tracker issue in the tracker entry’s namespace (
tracker {namespace, keys}). The issue comes from the branch (agent/issue/KEY, the claude-codeagent/{key}branch) first, then from a key in the title or description, and the key uses the issue generation current at the PR’s creation. The generation comes from the reopened generations’ Work the tracker keeps in the scope (bitbucket.StoreGenerations, below). Otherwise the PR keepspr/N. - Route (
bitbucket). It never creates Work. Input: merges, closes, finished checks, human comments and other agents’ comments. - Tools.
bitbucket.context,.pr.list,.pr.view,.pr.create(idempotent by state: an open PR from the branch is the answer; the body gets the issue referenceissue_ref),.pr.commentand.pr.reply. The comment tools use the outbox with an invisible Markdown mark..checkscompletes the set. - Webhooks.
X-Hub-SignatureHMAC,X-Event-Key, andX-Request-UUIDas the identity. Pagination never follows a link off the configured API.
PostHog
Section titled “PostHog”extensions/posthog runs as its own process: a package bundled with the release that open-conduit extension-run posthog serves through protocol/sdk/inprocsdk (#428), so its exporter receives batches over telemetry.export and its sink the host’s telemetry events over telemetry.sink. It is configured by a posthog connection (host, distinct_id, secret token = phc_…) and a non-ingress posthog entry in a scope (turns, telemetry_events, both default true, and records, default none).
- Exporter. It is on the observability pipeline. It sends
agent_turnfor each finished execution of the entry’s scope: outcome, attempt, executor andexecutor_binding(the configured binding, so two setups of one executor stay apart), work kind, duration, tokens and cost, plusstatus(the state the turn ended in, such as thework-issueplaybook’sbuilding,review,help,doneorfinished),started_at, andtrigger_typeandtrigger_atfor the event that started the turn. Time in a state is the gap from a turn’s event to the next turn’strigger_atfor the samework_id: the time after arevieworhelpturn is how long a person took to answer. A comment’strigger_typesays who wrote it:<source>.comment.human,.botor.agent. A failing PostHog loses telemetry (counted, reported as doctorcapture_failing) and never delays the runtime. - Lifecycle records.
recordsselects lifecycle records of the entry’s scope (records) to send as events named after them: exact names (work.transitioned), families (execution.*) or*for all. Each event carriesrecord,trace_id,event_id,work_id,execution_id,executor_binding,fromandtowhen present, every attribute under its record name (integers as numbers) and the usage fields ofagent_turn. The pipeline has already redacted them. Each exported batch goes to PostHog’s/batch/endpoint as one request per entry. Itsuuidderives from the scope, record name andrecord_id, so PostHog keeps one copy of a redelivered record.lease.renewedandingress.scan_pageare frequent, so name families rather than*unless you want them. - Host telemetry events. The host writes its telemetry events (reports, fixes, host readings) to its JSON log, then hands each one to every extension implementing
inproc.Sink:Capture(ctx, scope, name, at, props).scopeis the event’s scope, or""for one about the machine, andpropsnever carry it. PostHog sends a scoped event to that scope’s entries that settelemetry_events, and an unscoped one to every such entry, once per project anddistinct_idwhen several scopes’ entries share a connection. It adds the scope as thescopeproperty. - Dry run. A daemon dry run sends nothing.
Composition, doctor, setup and dry run
Section titled “Composition, doctor, setup and dry run”Linear, Jira and Bitbucket are written against the public extension/inproc contract and run as packages bundled with the release, each its own process served by open-conduit extension-run NAME through protocol/sdk/inprocsdk (#428): the trackers through the kit’s package entry point (extensions/tracker/pkg), Bitbucket through extensions/bitbucket/pkg. Each certifies with a fixture of its own fake service. Linear and Jira are the kit’s extension (extensions/tracker/extension.go) given each tracker’s validators and client, and Bitbucket has its own (extensions/bitbucket/extension.go). Through the contract each registers its types and route-config validators, its tools on every generation, its ingress sources at configure, and the webhook routes POST /<type>/<scope>/<entry> on the listener. Each also runs its own manage_webhook re-pointing, retried after a minute, and the trackers a background projection pass. A Bitbucket entry keys a reopened issue’s pull requests from the Work its tracker keeps in the scope (bitbucket.StoreGenerations): each reopened generation’s Work is keyed issue/<KEY>/generation/reopen-<unix> in the tracker’s namespace, so the generation current when the pull request was opened is the latest such reopen at or before it. It reads through the public store, so the tracker can run in another process. A pull request opened before the tracker has seen the reopen keys the earlier generation until reconcile sees it again.
- Doctor.
linear,jiraandbitbucket(registration, rate limit, projection, the entry’s webhook route and hook, dry run),linear-auth,jira-authandbitbucket-auth(check_secrets: sign-in; a bundled package’s check names begin with its own),posthogandposthog-auth(key form, a bundled package’s check names begin with its own; PostHog cannot be asked about a key without sending an event). Reconcile health is the host’sreconcile_lagcheck. - Setup.
init linear --teams ENG[,OPS]andinit jira --site … --projects …. Both take--bitbucket WORKSPACE/SLUG --bitbucket-username … --bitbucket-token REFand an optional--checkout. They write a claude-code binding on thework-tracker-issueplaybook (args {key}, branchagent/{key}) and validate offline. The playbook is the kit’s and, like the kit, names no other extension: it refers to the tracker’s operations as<tracker>.*and the code host’s as<host>.*, whichever the toolbelt lists. It leaves house workflow (how to sort issues, what a report says) to the project’s rules. Scopes stay workspace-free unless a checkout is given. - Dry run.
serve --dry-runmakes every tracker client write nothing. Projection, tools and webhook management reportdry_run.
Operator tools
Section titled “Operator tools”open-conduit adapters [TYPE] [--json]lists the config types of every package installed beside the config or bundled with the release, and the connection, secret, entry, route, binding, resource and tunnel fields each takes, as each package’s manifest describes them (fields,bootstrap.Adapters). Only the core’s own webhook listener (tunnel) is described by the core, and it lists the tunnel types the found packages declare. A package discovery refuses (an invalid manifest, or one from a newer Open Conduit) is missing from the list, andadapterssays so on stderr, one line per package.open-conduit route KEY [--scope S]explains how a key’s Events were routed: the owning Work, and for each correlated Event its disposition and the router’s durable decision (route, binding, generation, policy, reason). It reads them throughGET /v1/events/{id}/route, so the explanation is what execution used. The decision is recorded, not predicted.open-conduit config edit [--file F]is the full-screen editor. It shows the config tree and the catalog’s documented fields for the selected connection, entry, route, binding or resource, marking those present. It makes the same pointer edits asconfig set/unset, shows secret references redacted (and they must stay references), and saves only a document that validates offline.- Rendered activity. The Claude Code executor writes each turn’s
activity.txt(agent text, tool calls, tool results, the turn’s end) as the first run artifact. The TUI’s log view andruns logshow it;tcycles to the raw transcript and stderr. While a turn runs, the executor’sLiverenders the same activity from the segment’sout.jsonlso far and returns it with the transcript and stderr, sol,tandruns log --followwork before the artifacts are published. The executor owns the transcript format, so the TUI stays free of executor branches.
Evidence
Section titled “Evidence”- Unit and contract tests. Each extension’s tests include the shared ingress conformance, cases (wiki conversion, marks, pagination and fallback, transitions, reopens, webhooks and admin refusals, rate limits and credentials) and the kit’s route, generation and config cases.
- SQLite.
integration/tracker_test.goruns for both trackers: claim policy, input, labels and status convergence without feedback, a lost comment answer recovered after a restart, close and reopen generations, the tools, and Bitbucket PR, checks, comment and merge input to the tracker Work. - Daemon drill.
TestDaemonTrackersAndPostHog(inmake drill) runs Linear and Jira scopes, Bitbucket and PostHog on fakes. It coversos calltools, projection, PostHogagent_turn, doctor,routeandadapters, and a restart that posts nothing again. - Setup.
TestTrackerSetupScopesValidateOffline. - Editor.
TestConfigEditor.
Limitations
Section titled “Limitations”- No live evidence. Real accounts, webhooks, rate limits and admin hooks for Linear, Jira, Bitbucket and PostHog are untested; all evidence uses fixtures and fakes.
- Deleted issues. A deleted issue ages out as absence; it is not delivered as a close.
- Multi-scope routing. One entry per scope with a route
match, kept single-owner by the claimed label. Overlapping matches can double-claim a reopened generation, so keep matches disjoint. - Tracker waits and holds.
issue:/pr:waits and scheduled holds are GitHub-only. Tracker agents use the builtinwait. - Bitbucket comment tools. An ambiguous answer is confirmed by calling again; there is no background redrive.
- Comment detection. Linear comments on issues that are not listed are read only while the issue is listed: open, or updated within the horizon.
History
Section titled “History”- Ticket 06-01 delivered the shared tracker kit, the Linear, Jira, Bitbucket and PostHog adapters,
init linearandinit jira, and the operator tools (adapters,route,config edit, rendered activity).GET /v1/events/{id}/routewas added forroute. - All 06-01 evidence used fixtures and fakes. No real service was called; checks against configured accounts were left as live-operation evidence for the deferred tickets 03-07, 03-09 and 06-04, and the Linear mark’s rendering check was deferred to live use.
- Issue #357 moved Linear, Jira and Bitbucket off their bespoke bootstrap wiring (
internal/bootstrap/trackers.go) ontoextension/inproc. The onetrackersdoctor check became one per type, with each entry’s webhook route and hook in it rather than inwebhooks, and reconcile health left toreconcile_lag.