Stories · page 2 of 5
Jira Cloud as the story source: the filter, the mapping, what Foreman writes back, and signing in.
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 Rankto follow the board’s order. foreman doctorruns 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:
- Set up › Jira app walks you through making the OAuth app. Its two keys,
JIRA_CLIENT_IDandJIRA_CLIENT_SECRET, go in on their cards, or withforeman keys set JIRA_CLIENT_IDandforeman keys set JIRA_CLIENT_SECRETfrom a terminal. - 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-openprints the address instead, and--portchanges the port (change the callback URL to match). On a machine with no browser, such as a container,--pasteprints the address; approve anywhere, then paste back the address the browser lands on (the runner’s README shows how). - 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.siteis the site’s host, such asacme.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.
Was this page useful? Something missing or wrong?Email us and say which section.