Foreman›Docs›Stories›Stories from Jira

Stories from Jira

Stories · page 2 of 5

Jira Cloud as the story source: the filter, the mapping, what Foreman writes back, and signing in.

Foreman 0.11.07 sections

Foreman can take its stories from Jira Cloud instead of markdown files. Each issue that a JQL filter finds is a story, and the filter’s ORDER BY sets the order they are picked in. This page is for the person who connects a project to Jira: the filter, how an issue becomes a story, what Foreman writes on the issue, what happens when Jira does not answer, and signing in. Put this in foreman.config.json (Set up › This project and the Jira steps write it):

"stories": {
  "adapter": "jira",
  "site": "acme.atlassian.net",
  "jql": "project = ACME AND labels = foreman AND statusCategory != Done ORDER BY Rank",
  "ownerLabel": "foreman-owner",
  "transitions": { "started": "In Progress", "review": "In Review", "blocked": "Blocked", "shipped": "Done" }
}

The filter

stories.site is the site’s host and stories.jql the filter; both are required. Foreman reads every issue the filter finds, 100 at a time, following Jira’s paging. pollSeconds (default 60) is how long Foreman reuses the list before it asks Jira again; 0 asks every time.

JQL tips.

  • Select by a label, such as labels = foreman. When Foreman makes a follow-up issue for a shipped story’s open minor findings, the new issue copies its origin’s labels, so a label filter picks it up too. A filter on a list of keys never would.
  • Leave out finished work with statusCategory != Done.
  • End with ORDER BY Rank to follow the board’s order.
  • foreman doctor runs the filter and shows roughly how many issues it finds.

Every option of the block:

Name What it does Default
site The Jira Cloud site’s host, such as acme.atlassian.net. Required. none
jql The filter. Its ORDER BY is the pick order. Required. none
pollSeconds How long the last list is reused before Jira is asked again. 60
doneWhenField A custom field id that holds “Done when”, such as customfield_10050. unset: the heading in the description
ownerLabel Issues with this label are owner tasks and are never picked. foreman-owner
newIssueType The issue type of a follow-up Foreman creates. Task
transitions { event: transition name } for started, review, blocked, failed and shipped. {}: comments only
briefsDir Where the briefs are written, relative to the project. .foreman/stories

The mapping

An issue’s key is the story id (ACME-12), its summary is the title, and its “is blocked by” links are its dependencies. Its description becomes the brief. A “Done when” heading in the description, at any level, or the custom field named in doneWhenField, says when it is finished. Issues with the owner label (default foreman-owner) are yours to do and are never picked. The story’s position in the filter’s answer is its order; groups are not used.

A story is done when the issue’s status category is Done. Otherwise its status is the one Foreman last recorded (in-progress, blocked, ready-to-promote), else todo. What Foreman records lives in a state overlay, .foreman/stories/<key>.json, never in Jira, since the issue is not Foreman’s to rewrite.

A blocker that the filter does not list is still checked. When Jira says it is done it is dropped as met; when it is open, or Jira will not show it, the story waits, and foreman story plan names it as “outside the filter, not done” or “not found”. An owner task that waits on such an issue sits under Later on Waiting on you until it is done.

Before a story starts, Foreman fetches its brief and writes it to .foreman/stories/<key>.md (briefsDir moves it): the frontmatter (id, title, status, url), ## What from the description, ## Done when, ## Comments with the last 10 comments newest first, and ## Progress notes from the overlay. The agents read that file; the owner task card reads its headings too (see Owner tasks).

What Foreman writes back

Every comment starts with “Foreman:”.

When Comment Transition
A story starts “Started.” started
Its pull request is open “Pull request ready for review: <link> (commit <short sha>)” review
It is blocked “Blocked: <reason>” blocked
It fails “Failed: <reason>” failed
It ships the release note shipped

The transitions map names the workflow transition to apply for each event: started, review, blocked, failed and shipped. Foreman matches the transition’s name, or the status it leads to, ignoring case. Leave an event out to only comment for it. If the transition isn’t open from the issue’s current status, Foreman comments “could not move to <name> from <status>” and carries on. A Jira call that fails never fails the story. The write waits in .foreman/jira-outbox/ and is sent again, in order, once Jira answers (see When Jira does not answer). The same event with the same note twice in a row, such as a release run again, writes nothing new.

Two switches turn the writing off: foreman features off jiraComments stops the comments, and foreman features off jiraTransitions stops the moves. Both are on by default. A depends_on Foreman adds or takes off a story becomes an “is blocked by” link added or removed in Jira, whatever the two switches say. A blocker outside the filter that is done is never unlinked.

The story’s own record: notes on the issue

With jiraComments on, the story also holds the reasons it took the path it did, each as its own comment written in plain sentences (never a pasted file or a data structure), with the time and the slot at the end (“At 2026-10-31 14:05 UTC, slot w2.”):

  • every question Foreman asks you, with its options and the one it recommends, and your answer and note, quoted as you wrote them;
  • the architect’s design, in full: the builder it chose and what that builder is for, the design, how it will be tested, when it is done, the files it expects to change, anything it needs for itself, and, when it cannot be built yet, what it waits for or the choice it asks of you. A design longer than a Jira comment is split into numbered parts (“part 2 of 3”). When the plan changes later, a “design (revised)” comment lists only what changed;
  • each round of review: approved, or sent back with the findings;
  • how a session stopped (no progress for too long, a crash, the Claude sign-in, the usage limit, an outage) and what is kept for the resume;
  • a release that stopped, with the reason and the pull request;
  • a follow-up Foreman created from the story, with its key.

A note that was already posted is never posted again, even when a command runs twice. features.jiraCommentDetail is summary by default, which counts a round’s findings (“3 findings: 2 blockers and 1 major”); full lists each one, such as “The tester found that the retry never stops (plugin/lib/engine/retry.mjs, line 42): the loop has no limit. Fix: cap it at three.” The design is always posted in full. The setting applies from the next story. A project whose stories are markdown files gets one progress line for each of these instead (the title and a short summary). With jiraComments off, none of this is recorded. Secrets, and the folders on your computer, never appear in a comment.

How it works: a note is queued first, in .foreman/story-notes/pending/, and posted by whichever process flushes next, oldest first, under a lock, so no command or agent waits on Jira. A ledger per story in .foreman/story-notes/posted/ remembers the last 1,000 notes it has, which is what keeps a repeated command from posting twice. A comment holds 16,000 characters (Jira refuses 32,767); a longer note is split on the blank lines between its paragraphs into parts headed “Foreman: <title> (part i of n)”, never inside a code block while the block fits, and the parts go in one send. A note on an issue the filter no longer lists still lands when Foreman tracked that story, so a shipped story gets the note about its follow-ups.

foreman story note <id> --kind design --file <plan> records a design by hand from a saved plan file. A design already recorded is not recorded again; a plan that changed since is recorded as a revision listing only what changed. design is the only kind the command takes; a file it cannot read, or one with no design text, exits 1; with the comments switch off it says so and exits 0.

Follow-ups created without asking

foreman learn turns the minor findings a shipped story left behind into a follow-up story. It runs in the background after each shipped story and when the run reaches standby. By default it asks first, as a decision on Waiting on you titled “Follow up on ACME-12: 2 minor finding(s)?” with “Create a follow-up story” and “Ignore”; see How Foreman learns.

With features.autoCreateFollowUps on (off by default), learn creates each follow-up at once instead. The new issue goes in the origin’s project with the type newIssueType (default Task), the title “Follow-ups from ACME-12”, the origin’s labels less the owner label, the findings as its description with a “Done when” heading, priority Low where the project has that priority (made without it where it refuses), a Relates link to the origin and a Blocks link from each dependency. A link Jira refuses is logged as an event; the issue still counts as made.

One note under FYI on Waiting on you then names what was made: “Foreman created 2 follow-up stories”, with the summary “ACME-20 and ACME-21 to fix the minor findings of ACME-12”, and two options. “Undo and close them” runs foreman learn undo <id>: each new story still waiting to be built is closed as Done with a note, one already started is left as it is, and the findings are not proposed again. “Keep them” clears the note and the run builds them like any other story. While follow-up proposals wait on you, the standby line says how many instead of “nothing to build”. A create that fails falls back to asking you.

The record of each follow-up is also a comment on the origin issue (the last kind in the list above), and the new issue’s follow_up_of is the origin, so features.followUpsOfFollowUps (off by default) can keep follow-ups from chaining.

When Jira does not answer

Every Jira call a command makes runs in a child process and gives up after 30 seconds. The supervisor never waits on Jira at all: a refresher of its own polls the filter every pollSeconds (at least every 5 seconds), each request giving up after 60 seconds, and writes the answer to .foreman/jira/list.json. Commands and the supervisor share that cache, so one poll serves everyone.

When Jira cannot be reached, the last answer of any age stands in, so a story under way can still record its progress. Picking stops: while the last refresh failed, or the cache is older than 5 × pollSeconds, the run shows “Jira not reachable since HH:MM: <why>; picking paused, stories in progress carry on”. On Waiting on you that is an FYI, “Picking paused”, for its first 30 minutes, because a sleeping computer or a dropped network causes most of them and the refresher retries every minute; after 30 minutes it moves to Needs you now as “Jira still not reachable”, with the advice to check the network or foreman jira status. When the sign-in itself has lapsed, you get the paused notification with “Jira sign-in expired; run foreman jira login”.

A write Jira does not take (a comment, a move, a link) goes to the outbox, .foreman/jira-outbox/, one small file per write, and is sent again in the order it was made: before the next write, or after the next poll that reaches Jira. Each queue, replay and drop is an event in .foreman/events.jsonl (“Jira: could not send a comment on ACME-12 (…); queued to retry.”, “Jira: sent the move of ACME-12 to Done from the queue.”). A write Jira refuses outright, such as a transition that does not exist, is dropped with a warning rather than retried for ever; a queued file that cannot be read is dropped the same way.

Signing in to Jira

Foreman signs in to Jira Cloud with OAuth 2.0 (3LO). Set it up once per project, on the Set up page or with foreman setup next:

  1. Set up › Jira app walks you through making the OAuth app. Its two keys, JIRA_CLIENT_ID and JIRA_CLIENT_SECRET, go in on their cards, or with foreman keys set JIRA_CLIENT_ID and foreman keys set JIRA_CLIENT_SECRET from a terminal.
  2. Set up › Jira sign-in signs you in (foreman jira login) and, when your sign-in reaches more than one site, asks which one this project uses. The command opens the consent page; --no-open prints the address instead, and --port changes the port (change the callback URL to match). On a machine with no browser, such as a container, --paste prints the address; approve anywhere, then paste back the address the browser lands on (the runner’s README shows how).
  3. Set up › Jira site and filter picks the Jira project and the JQL filter, shows the issues it finds, and sets the moves and the owner label. stories.site is the site’s host, such as acme.atlassian.net.

The keys are saved in .env.foreman at the project’s root (config foremanEnvFile). That file holds Foreman’s own keys: it stays out of git (foreman doctor adds it to .gitignore) and out of the story workspaces, so the agents never see it. The project’s env file (.env.local) is linked into every workspace for builds and tests, so Foreman’s keys stay out of it. If they are already there, foreman keys migrate, run from the project’s folder, moves them to .env.foreman and leaves the rest of .env.local as it was; foreman doctor warns until it is done.

The tokens are saved to ~/.config/foreman/jira-<site>.json (mode 600), never in the project, and are refreshed on their own. foreman jira status shows the site, the account, the token’s expiry and whether stories.jql runs. foreman jira logout deletes the file. If the sign-in expires (90 days unused, or a changed password), run foreman jira login again.

foreman doctor checks the Jira setup: that you are signed in, that the sign-in was renewed in the last 80 days, that the JQL runs, that the transition names exist, and that the two keys are set. It shows the keys’ names only, never their values. The checks are listed on Doctor checks; the Jira settings on Settings and switches.