CogniRunner 3.3.0: it no longer waits for a transition
Gabriela Perdum
Author
14 min readSeptember 9, 2026
Key takeaways
CogniRunner 3.3.0 (Marketplace, 7 September) adds listeners and scheduled jobs. Until now every rule needed a workflow transition to fire; now a rule can react to any of 68 Jira events or run on a five-field cron in an IANA time zone.
Loops are the failure mode of event automation, so the guard rails are on by default: self-generated events are ignored unless you say otherwise, and brakes cap a listener at 120 runs and an issue at 30 runs in any five-minute window.
Redelivery and loops are different problems. A brake counts runs; a claim makes sure a redelivered event, or a duplicate scheduler tick, runs once — a wall-clock job's claim is keyed on the schedule's local minute, so a DST fall-back cannot fire it twice.
The Rules REST API provisions listeners and jobs as JSON, one or up to 100 at a time, behind a bearer token whose SHA-256 hash is the only thing stored. It does not provision workflow rules; those still attach through Jira's own workflow API.
The app's internal notes call this release 1.2.0. The Marketplace, which is the number you will see, calls it 3.3.0.
Every CogniRunner rule so far had one way to start: an issue crossed a workflow transition, and a validator, condition or post-function ran. That is a good place to put a rule, because a transition is a moment somebody chose. It is also a narrow one. A comment arriving, a sprint closing, a field being edited, or simply Tuesday at two in the morning are not transitions, and an automation that cannot see them is only ever half of what a Jira admin needs.
Release 3.3.0, on the Marketplace since 7 September, adds the other half. A listener reacts to a Jira event. A scheduled job runs on a cron expression. And a Rules REST API lets you create both from a script instead of a screen. The product page carries the full specification of each; this post is about what they do and why they are built the way they are.
One note on the number before anything else. The app's own release notes call this release 1.2.0, and the product page says "new in 1.2". The Marketplace, which is the version you will see when you install or update, calls it 3.3.0, released on 7 September after 3.2.0 on 27 August and 3.1.0 on 13 August. Same release, two numbering schemes. This post uses the Marketplace one.
Listeners: react to the event, not the transition
A listener picks one or more Jira events and says what should happen. The catalogue has 68 of them, registered in the app's manifest across six groups: the issue itself (created, updated, assigned, commented, mentioned, viewed, deleted, and the comment events), things attached to an issue (worklogs, attachments, links), project-level changes (projects, versions, components), Jira Software (sprints and boards), administration (users, fields, field contexts, issue types, filters, configuration), and Jira Service Management request types. The catalogue in code and the manifest are held in lockstep by a test, so the number in the picker is the number the app is registered for. Registered is not the same as delivered: the review records note that the three Service Management request-type events were never delivered on the development site during testing, and they say plainly that this is not a claim that all 68 passed live.
Picking an event is the easy part. The filters are what make a listener usable: by project, issue type, JQL, which fields changed, or a regular expression over a comment. On top of those there is an optional condition written in plain language, evaluated by the model, and it fails closed. If the AI condition cannot be evaluated, the listener does not run. That direction matters. An automation that acts when it cannot decide is worse than one that stays quiet, and the log says exactly which happened.
What runs is one of two things. Code steps use the same sandbox as the static post-functions, with the same api.* surface, bound to the event's issue. Or an AI agent runs with instructions and an allow-list of actions it may take. The agent cannot do anything outside that list, and its rounds are capped.
Why the guard rails are on by default
Event automation has one classic failure mode, and every Jira admin who has written a "when updated, update" rule knows it: the rule triggers itself. Your listener comments on an issue, the comment is an event, the listener fires again.
CogniRunner's answer is two mechanisms that solve two different problems, and it is worth being precise about which is which.
The first is the self-event guard. By default a listener ignores events the app itself generated. The setting exists in the listener's definition and you can turn it off, but the default is the safe way round, and a listener you did not think about stays out of its own feedback loop.
The second is a pair of brakes. Each listener may run at most 120 times, and each issue may trigger at most 30 listener runs, in any five-minute window. When a brake trips, the app logs one line saying so, and then suppresses further runs in that window without logging each one. That last detail is deliberate: a loop that logs every suppressed run is a loop that fills your log instead of your issue, and the point of a brake is to make the loop stop costing you anything.
Brakes and the guard are about loops. Redelivery is a different problem. Forge can deliver a product event more than once, and an automation that runs twice because the platform said the same thing twice is not looping, it is duplicating. So every accepted event is claimed before the AI gate or the sandbox runs, and a redelivered event finds the claim and is skipped. The release notes phrase this as at-least-once delivery, which is exactly right: the platform delivers at least once, and the claim is what makes the run happen once. Note the edge the code comments on itself: a run that claimed and then crashed is not replayed with its completed writes intact. The claim protects against duplicates, not against a crash mid-run.
A listener can be tested with a real issue before it goes live. The app builds a synthetic event from the issue you pick and runs the whole thing in simulation. The docs are honest about what that exercises, and the sentence is worth quoting: it "checks matching and execution, not delivery, brakes or the self-event guard". No brakes, claims or statistics are consumed by a test run.
Once a listener has fired for real, its last payload can be inspected as a sample, and the samples are references for the editor, not execution payloads. Any Forge context token is stripped at every depth, on capture and again on read, and free text is replaced by a placeholder that keeps the length. The sample tells you the shape of what arrived, which is what you need to write the next filter, and nothing you would not want stored.
Scheduled jobs: cron, with an optional scope
A scheduled job runs on a five-field cron expression, minute hour day month weekday, in an IANA time zone. If the runtime does not know the zone you named, the job falls back to UTC rather than refusing to save. A job runs either once with no current issue, or once per issue of a JQL scope, capped at 100 issues, which is the shape of an escalation service: every Tuesday, for every issue matching this query, do this.
The platform tick is five minutes, so a schedule of every minute runs once per tick. A job can also be run manually from the editor or through the API.
The interesting engineering is in what happens when the tick misbehaves. Forge can deliver a scheduled tick twice, the same way it can deliver an event twice, and a job that double-runs because of it would be a serious defect in anything that posts comments or moves issues. So each due job is claimed per minute before it is queued. The identity of that claim comes from the schedule, not from the raw instant: for a wall-clock schedule like two in the morning, the claim is the local minute. The documentation and the last-minute release commit both call out why that matters. On a daylight-saving fall-back night, two in the morning happens twice; keyed on the raw instant, a job would fire twice, and keyed on the local minute it cannot. Spring-forward gets the mirror treatment, so the skipped hour does not skip the job. Missed ticks are replayed, but only for the last hour and only one run, so a long outage does not end in a burst of catch-up runs.
Scoped runs record a per-issue outcome, and the history view shows them in full, so a job over 60 issues that succeeded on 58 tells you which two and why.
The Rules REST API
Both kinds of rule can be created, updated, enabled and disabled over REST, as JSON; a listener can be tested and a job run the same way. The intended user is a CI pipeline or a migration script: provision the same 40 listeners on a new site without clicking 40 times.
You mint a bearer token in Settings under API access. Minting is admin-only, the plaintext is shown once, and what the app stores is the token's SHA-256 hash, its prefix, who created it and when it was last used. A request authenticates with the bearer header or an X-Api-Key header, and the token is checked against the hash.
Rows created this way are tagged with the token that made them, so a listener's creator reads as the token id rather than a person, and an audit later can tell which pipeline put it there. You can send a single object, or an array of up to 100. Updates are partial, merged over the existing row. When some rows in a batch save and others fail, the response is HTTP 207 with the index of every rejected row; if none saved it is a 400, and if all saved it is a 200, or a 201 for a single new item.
One scope decision is worth stating plainly because it is easy to assume otherwise. This API provisions listeners and scheduled jobs only. Workflow rules, the validators, conditions and post-functions that started the app, still attach through Jira's own workflow REST API, because a workflow rule belongs to a workflow and Jira owns that object. The listeners and jobs belong to the CogniRunner installation, and that is why they get their own endpoint.
The agent, and what it is allowed to touch
The second thing a listener or a job can run is an AI agent, and it is worth being exact about how much rope it has. The operator writes instructions. The model receives the event or job context as fenced, untrusted data, the same treatment the validators give reference documents, so a comment body cannot become an instruction. And the agent can only act through the actions the operator ticked. There are thirteen of them: get an issue, search issues, add a comment, update fields, add or remove labels, set the assignee, transition an issue, create an issue, link issues, add a watcher, send a notification, and add a worklog, plus an implicit finish. Nothing else exists for it to call.
Each of those calls goes through the same sandbox API as a code step, which means the same simulation mode, the same kill switch, the same change ledger and the same transient retries apply to an agent's action as to a line of code you wrote yourself. Rounds are capped between one and eight, five by default, and the summary and every tool call land in the execution log. An agent that cannot explain what it did in the log did not do it.
Samples carry shape, not content
The last-seen payload sample deserves a closer look, because it is the piece that makes writing a filter tractable and the piece most likely to worry a security reviewer. A sample is kept per event type, for seven days, and only when a listener subscribes to the event and its project filter accepts the payload; an event nobody listens to leaves nothing behind.
What is kept is the shape. Inside the issue fields, the comment, the worklog, the changelog and any user object, every string is replaced by a placeholder that records its length, and rich text by an empty document. What survives is schema: ids and keys, field ids, timestamps, and the names of statuses, status categories, priorities, issue types, resolutions, projects and link types. Summaries, string custom fields, labels, descriptions, comment bodies, every changelog from-and-to value, and every display name, email and avatar URL are placeheld. The editor shows the sample next to the code steps as the exact shape of the event object your step will receive, and that is all it shows.
Budgets and hard caps
A few numbers from the code define the edges of what a rule can do, and they are better known before a design depends on them. A listener run has a budget of 105 seconds inside Forge's 120-second consumer limit, and the trigger that matches events has 18 seconds inside the platform's 25. An installation holds at most 200 listeners and 200 jobs, and at most 25 live API tokens. A REST body is capped at 512 KB. The trigger's index of listeners is cached for 30 seconds per warm container, so a freshly saved listener can take up to half a minute to start matching, which is worth knowing before you conclude it is broken. Statistics can appear shortly after the execution log while their accounting task runs, and clearing history preserves run counts rather than resetting them.
Things the docs say that the release summary does not
A few operating facts from the documentation belong in any honest description of this release.
Event delivery is not instant. Forge delivers product events up to about three minutes after the action, so a listener is eventually consistent, typically within seconds, and a rule that assumes the world has not moved since the event was raised will occasionally be wrong. The issue-viewed event fires on every view, and every view invokes the app even when no listener uses it, at the cost of one cached read; the picker flags it as high volume, so use it with a narrow filter or not at all. Some events are hard to see at all: user events need real user provisioning, the failed-expression event needs a workflow expression that actually fails, and the field-deleted event only follows a trash and a permanent delete.
The scheduler's granularity is five minutes. Daylight saving follows Vixie cron. A schedule with an explicit minute and hour is anchored to the local clock, so each local time fires exactly once: the repeated hour of a fall-back does not run it twice, and a time inside a spring-forward gap runs once at the first instant after the gap. A schedule whose minute or hour is star-based is anchored to elapsed time and keeps its rhythm, which means it fires in both two o'clock hours of a fall-back. And listeners and jobs run as the app, not as a user; there is no run-as-user.
The release also fixed a class of quiet bugs, and they are the kind that matter more than a feature. A step that throws a string, a number or null is now reported as the failure it is, instead of aborting the steps after it, and the Fix with AI feature learns from the step that actually failed. Sandbox methods that take an optional issue key default to the current issue, but an explicitly empty key now throws rather than silently writing to the bound issue, which is the difference between a typo and a wrong issue edited. Simulated create-issue and clone-issue calls return distinct, Jira-shaped identities that later steps can use, and a simulated read never reaches Jira, so the workflow Test Run, which now uses the same simulation as listener and job tests, tells you what a rule would do without doing any of it. Attachment read and upload capabilities are claimed atomically, so a replayed link can never be used twice, and a generated document is only reported as attached when Jira returned a concrete attachment id. Run and error counters are accounted through serialized receipts, so concurrent runs cannot lose or double-count a result, and deleting a rule clears its statistics atomically. The admin panel stays usable at Jira's narrowest iframe width. Under the hood the app moved to @forge/api 7.2 and @forge/events 2.1.7.
What was verified, and what was not
The repository carries review records from the first week of September that show the features exercised against a live Jira on a development environment: real scheduled ticks with a comment read back independently, 19 of 19 live scheduled-job checks passing, 32 rules provisioned over REST and read back, single creates returning 201 and partial updates preserving their siblings, and the cleanup afterwards, 22 campaign listeners, 10 jobs and 6 fixture issues, confirmed by 404s. The same records count 36 findings fixed and 3 parked with platform evidence, and 52 of 52 offline suites passing on the final combined code. They also say plainly that production had not been deployed at that date. The only production-facing fact is the Marketplace release on 7 September. Nothing here says how many sites run it, because nothing in the sources does.
If you want to see the listener and the scheduled job built end to end, the release notes link nine videos: seven short walkthroughs it describes as one per feature, including one that auto-triages new issues with a listener and one that flags unassigned tickets on a schedule, a complete walkthrough, and a five-minute compilation. And the two earlier CogniRunner pieces, on real workflow conditions in 3.1.0 and on giving a rule a memory, cover the transition-bound side of the app that this release stops being the only side of.
I said the certificate was a receipt. Then I collected four of them in two months
Four ACPs, a gruelling two months, and an Atlassian Certified Expert badge at the end of it. The gamble was harvesting every dump site on the internet and refusing to believe a single answer until the documentation said so — and the free practice exams that came out of it now cover all five ACPs.