a9script

Case study

Jira to Linear, built by an agent

Eleven minutes from prompt to the last of six test runs, measured on 2026-09-02 on the owner's own tenant. One session, not a benchmark: your API, your prompt and your agent decide. The connections, credentials and the webhook endpoint existed before the prompt.

The setting

Tenant Demo, sandbox environment. Entered by a person in the browser before the prompt: the JIRA connection; the Linear connection; the jira webhook endpoint; two configured calls: JiraGetIssue, LinearGetIssues. The agent's token was minted for this environment with the credential facet credentials off, so it could neither create nor repoint a connection; write probes were off.

The prompt

Client: Claude Code with the a9script authoring skill, over the MCP authoring surface.

Build a JIRA to Linear Integration. There are already connections to JIRA and Linear, and a configured Endpoint for JIRA webhooks. Whenever there is a new Issue in JIRA, a Linear Ticket should be created. A new comment should be written to the right Linear Ticket. If there is a status change in Jira just write it as a comment to linear. Every hour check the linear issues if they are still open. If they are completed already, write a comment to the corresponding JIRA Issue and clear the connection - further updates from JIRA will not longer be transfered to Linear. Do it in a separate automation called JIRA2Linear and prefix all new entities you use with "J2L". Use as much config functions as possible. Do not change existing scripts or functions. Build it on its own just by using the existing connections.

What the agent did

Transcribed from the owner’s screenshots of the session on 2026-09-02. Each row says whether it is SEEN — in the transcript or a screenshot — or BY THE LOOP: the authoring skill requires the move, and the screenshots do not show it. Nothing here is inferred from what the platform would do.

#What the agent didEvidence
1Loaded the a9script authoring skill.seen
2Listed the environment’s entities: tenant Demo, sandbox; two connections (jira, linear), two configured calls (JiraGetIssue, LinearGetIssues), one endpoint (jira).seen
3Read the jira endpoint’s own description.seen
4Probed both existing calls to see their real answer shapes (JiraGetIssue for KAN-8, LinearGetIssues).seen
5Read the authoring pack: the model of a script, then the function families it needed (core, mapping, state).seen
6Tried LinearGetIssues with a query argument and was refused: the function declares no parameter named query. It followed the refusal’s own next action.seen
7Created a discovery call, J2L: Linear Get Teams, as a file: apply —dry-run first, then apply, then probed it and read the team id off the answer.seen
8Wrote the integration as files: five configured calls, one settings script, four main scripts, one automation.seen
9Applied the folder.by the loop
10Ran six test runs with simulated events: an issue created, a comment, a status change, the hourly check, a comment carrying the prefix, an update without a status change.seen
11Checked the loop guards: the prefixed comment and the status-less update were skipped.seen
12Reported what it could not verify: live webhook delivery, one write probe the sandbox’s switch refused, the disconnect flow.seen

What it built

Configured calls (five): J2L: Linear Create Issue (GraphQL mutation) · J2L: Linear Create Comment (GraphQL mutation) · J2L: Linear Get Issue (GraphQL query, status by id) · J2L: Linear Get Teams (GraphQL query, used to discover the team id) · J2L: JIRA Add Comment (REST POST).

Settings (one): J2L Settings — the Linear team id, the mapping namespace j2l-issues, the comment prefix [J2L].

Main scripts (four): J2L: On JIRA Issue Created (webhook, issue created — creates the Linear issue, stores the mapping, comments back on JIRA) · J2L: On JIRA Comment (webhook, comment created — forwards the comment to the mapped Linear issue) · J2L: On JIRA Status Change (webhook, issue updated — forwards a status change as a comment) · J2L: Poll Linear Status (schedule, every hour — checks every mapped Linear issue; completed or cancelled means a comment on the JIRA issue and the mapping deleted).

Automation (one): JIRA to Linear, grouping the four scripts.

Loop protection, as the agent stated it: every JIRA comment the automation writes starts with [J2L]; the comment script skips a comment starting with that prefix; the update script skips a webhook whose changelog carries no status change.

The test runs

ScriptInputResult
J2L: On JIRA Issue Createdsimulated issue created for KAN-99created LEN-6 in Linear, mapping stored
J2L: On JIRA Commentcomment on KAN-99forwarded to LEN-6
J2L: On JIRA Status ChangeTo Do → In Progress on KAN-99forwarded as a comment to LEN-6
J2L: Poll Linear Statusemptychecked one mapping, LEN-6 still open, nothing closed
J2L: On JIRA Commenta comment carrying the prefixskipped, as the loop guard requires
J2L: On JIRA Status Changea description change, no statusskipped

Then a real ticket

A person created KAN-9 in JIRA by hand. The webhook reached the sandbox endpoint, the script ran, LEN-7 appeared in Linear with the JIRA key in its title, and JIRA received the comment [J2L] Linked to Linear: LEN-7 with the link to the Linear issue — written by the agent's own comment call, through the connection a person had entered.

The session's screenshots are the owner's and are added to this page as they are supplied, dated and masked.

What a person did

  • Entered the JIRA connection, the Linear connection and the jira webhook endpoint in the browser, before the prompt — a credential is a person's to enter.
  • Minted the agent's token for the sandbox without the credential facet, so it could neither create nor repoint a connection.
  • Wrote the prompt, and read what came back.
  • Created KAN-9 in JIRA by hand, watched LEN-7 appear in Linear, and read the linking comment on KAN-9.

Take-aways

  • The agent never held a credential. It named the connections; the platform attached the credentials when the calls left.
  • Only the logic is code: five calls, one settings script and four short main scripts. Retries, the record of every call and the id mapping were the platform's.
  • A refusal taught it: an argument the function did not declare was refused by name, with the next move in the refusal, and the agent followed it.
  • What it could not verify, it said — the block above is its own list, not ours.