Documentation / Kanban+

SLA tracking

Last updated: 6 September 2026

Kanban+ can hold each board column to a time limit: how many days a card is allowed to sit there before it's flagged, and how long before that you'd like a warning. Cards past their limit get a badge, column headers count them, and automation rules can act on them.

This page is honest about what the clock measures and what it doesn't, because that's where the surprises are.

What the clock measures

Time in the current status, not time since the issue was last touched.

The distinction matters. An issue's updated timestamp changes on any edit — a comment, a label, an assignee swap — so a card that's been stuck in Review for three weeks looks fresh the moment someone comments on it. Kanban+ instead records the exact moment an issue entered its current status and measures from there.

That record is written by the app's Jira event listener: every time an issue changes status anywhere on the site, and every time an issue is created, Kanban+ stores { issue, status, entered at } in its own storage. The board reads those records in batches and validates each one against the issue's current status before trusting it. If the stored status no longer matches — or there's no record at all — the board falls back to the issue's last-updated time and says so implicitly by still showing an age.

The hourly SLA checker is stricter: an issue with no stored entry timestamp is skipped entirely. It cannot breach, and no sla_breach rule will ever fire for it.

Backfilling older issues

Issues that last changed status before Kanban+ was installed have no timestamp. Until you seed them, SLA automation ignores them.

Settings → Status time accuracy → Backfill historical issues fixes that. It walks each board issue's Jira changelog and writes an entry using the timestamp of its most recent recorded status transition. It only writes where no entry exists, so live data always wins and running it twice is safe. When it finishes it reports how many new entries it wrote out of how many issues it scanned.

It scans at most 500 issues per run, and that ceiling is not visible in the UI. On a board with more issues in scope, the run covers whichever 500 the query returns and reports "out of 500 issues scanned" as though that were the whole board. The issues it didn't reach keep their missing timestamp, and the hourly checker keeps skipping them. If your board is larger than 500 in-scope issues, narrow the board — or accept that SLA automation covers part of it.

Run it once per board after installing, and again after adding projects to a board's scope.

Setting the thresholds

SLA thresholds are per column, and they live in one place: the Board tab → Configure columns.

FieldMeaning
SLA daysThe limit. Past this, the card is breaching. Leave blank for no SLA on this column.
Warn atThe earlier warning. Only appears once a limit is set; clearing the limit clears it too.

Both fields hint at steps of 0.5 with a minimum of 0.5, but that hint is only a property of the form — any number above zero is accepted, stored and used, and nothing validates it on the server. It won't help you either way: ages are floored to whole days everywhere, so a fractional threshold behaves as if you had rounded it up to the next whole day. Use whole numbers.

The Settings tab has its own column editor, and it exposes WIP limit and WIP mode — but not the SLA fields. If you're hunting for them there, that's why. Use the Board tab's editor.

What you see on the board

BadgeCondition
🕐 SLA +Nd (red)Age is at or past the limit. N is how many whole days past.
⏰ Nd left (amber)A warning threshold is set, and age has reached it but not the limit. N is days remaining, rounded up.
(nothing)No limit set on the column, or the age can't be determined.

Column headers show a breach count and a warning count. Cards in Done-category columns are excluded from those counts.

Acting on a breach

Create an automation rule with the trigger SLA threshold breached, pick a column, and choose whether it fires at limit or at warning. Then add conditions and actions as with any other rule — see Automation.

The rule is evaluated by a job that runs once an hour. For each board that has at least one enabled sla_breach rule, it:

  • sets aside any rule the board's permission verification does not cover (see Automation → Permissions) — such a rule is skipped, and the audit log says so once per rule per day; if no rule on the board is runnable, the board is not scanned at all
  • builds a query — project in (…) for the board's projects, and the board's Board filter (JQL) if it has one — then adds statusCategory != Done and pages through the issues
  • reads each issue's stored status-entry timestamp, and skips the issue if there isn't one
  • maps the issue's current status to a board column, and skips it if the status isn't in any column
  • works out days in column as floor((now − entered at) / 24 hours)
  • compares that to the column's limit (or warning threshold, for a at warning rule) and skips anything under it
  • checks the cooldown, evaluates the rule's conditions, runs its actions, and writes an audit entry

The project clause is always there, and the filter only narrows it. The scan can never reach a project the board doesn't cover — the projects are what the saving administrator's permissions were verified against, and the filter is ANDed onto them, the same way the snapshot and cross-board scans have always done it. (Earlier builds ran the filter instead of the project list whenever one was set, which let a scan wander into projects outside the board; that is fixed.) Quick filters and the board view's updated >= -30d condition still play no part; this is not the query the board itself draws. A board with no projects — scoped only by a filter — is not scanned at all, and such a board will refuse to save a rule that changes issues.

A 24-hour cooldown applies per rule, per issue. Without it an hourly job would re-fire on the same breaching card 24 times a day. The cooldown is written before the audit entry, so a crash mid-run can lose a log line but cannot re-run the actions.

A column with no threshold configured is skipped, and a rule pointing at such a column never fires.

What SLA tracking is not

This is a straightforward day counter, not a service-desk SLA engine. Specifically:

  • Calendar days, not business hours. There is no working-hours calendar, no holiday schedule, no weekend exclusion. A card sitting over a long weekend accrues three days.
  • Whole days, floored — badges included. Everything counts completed 24-hour periods: the checker, the card badges, the column-header counts and the CSV export all work from the same floored day count. A limit of 0.5 days shows nothing for the first 24 hours, and the moment it would fire the card is already a whole day old. Fractional thresholds are meaningless everywhere, not just in automation.
  • One-hour granularity. The checker runs hourly, so a breach is acted on up to an hour after it happens.
  • No pausing. There's no "clock stops while waiting on the customer" state. The only way to stop the clock is to move the issue to another status.
  • Per column, not per issue type or priority. Every issue in a column shares the column's limit. You can narrow which issues an SLA rule acts on using the rule's conditions, but you cannot give a Bug a different threshold from a Story in the same column.
  • No SLA history or report page. There's a badge, a header count, and the automation audit log. There is no chart of breaches over time.

Troubleshooting

No badges appear at all. Check the column has an SLA limit set — in the Board tab's column editor, not Settings.

Badges appear but the automation never fires. Most often this is a missing status-entry timestamp on older issues: run the backfill. Also check the rule is enabled, its column matches, and its threshold choice (at limit vs at warning) matches the threshold you actually set.

The rule fired once and then stopped. That's the 24-hour cooldown doing its job. It will fire again tomorrow if the issue is still breaching.

The age looks wrong right after a bulk status change. Records are written from Jira events, so give it a moment; if the stored status doesn't match the issue's live status, the board discards the record and falls back to the issue's last-updated time.

Related