User guide: contents

How issue watchers use labels

For each issue with the trigger label the watcher sets status labels, so you can follow along in GitHub:

Label (default name)Meaning
factory:workingWorking — nothing to do, it continues by itself
factory:needs-infoWaiting for you — questions: reply on the issue, or reply /defaults
factory:waiting-approvalWaiting for you — approval: reply /approve or /reject on the issue
factory:doneDone — nothing to do
factory:failedFailed; the comment on the issue starts with what you need to do, then says what happened, why, the kind of problem, what was already tried and your options, with the failing output. Remove the label to start over, or resume the run on its page to continue at the failed step. If the Foundry itself failed, the comment says so and names the fix

The descriptions on GitHub say the same in short. They are set when the labels are created and refreshed at every server start. The trigger label and the review label (vars.review_plan_label) get one too. If the review label is the same as the trigger label, the one description says both.

Each watcher's card on the Watchers page lists the labelled issues it is not working on right now under What happens next, with the same lines as the Dashboard. A watcher error is a line too; the raw error is under Error details.

Every check also compares the label with the newest run and fixes what is safe to fix: a run that is working (for example approved or resumed on its page) gets factory:working, and a label that does not match the run (say factory:failed while the run waits for approval) is corrected. A factory:done label is left alone, and an issue without a status label starts over. If you close an issue on GitHub while its run is still working or waits for approval, nothing is changed; the watcher says so (see Why is nothing happening?) and you decide whether to cancel the run. Every label change is in the card's Recent activity. Next to the repository the card shows last successful check …, also when the newest check failed.

The needs-info and waiting labels stay only while the Foundry really waits for you. Once you have answered the questions and the issue waits for something else (another story, the release pull request, the budget, the limit of new stories per check or a story that is running), the label is removed at the next check, so the issue looks like every other story that waits. When the release pull request holds it, the thing to do (merge it) is on that pull request. A run that asked, was answered and cannot continue yet shows factory:working. The label comes back only for new, unanswered questions. A waiting label on an issue that has no run is left alone: remove the label to start the issue.

Parked stories. To park a story, remove its Foundry labels (or only the trigger label). A run that stopped with questions, or was cancelled while it was stopped, is then parked at the next check: the watcher marks the run as parked, and the overview shows "parked — not tracked by a watcher" with nothing to do. Nothing continues the run, and answering on the issue does not start it. Needs you leaves the story out too. Add the trigger label again and the story starts over with a new run, which the overview then follows; the old stopped run does not block it. A story that still has the trigger label after its stopped run was cancelled is not started again by itself; the overview says "cancelled — nothing to do". To start it again, remove and add the trigger label, or resume the run on its page. Stopped runs that wait for a release or a code area are not parked.

More options are set in ~/.spaghetti-code-foundry/config.yaml (the form keeps them when you edit the watcher). This is the watcher for the one-label pipeline:

watchers:
  - id: webshop
    source: issues
    flow: issue-gitflow            # feature branch per issue → merged into develop
    precheck_flow: epic-questions  # ask all open questions for new issues first
    github_repo: acme/webshop
    label: Factory_go
    exclude_labels: [wontfix]
    status_labels: {working: Factory_working, done: Factory_done, needs_info: Factory_needs_info, waiting: Factory_waiting, failed: Factory_ERROR}
    remove_on_done: [Factory_go]
    max_per_tick: 2                # issues on different code areas are coded in parallel (bug stories are not counted)
    priority_labels: [bug]         # stories with these labels go first (the default)
    dependency_done_labels: [Factory_done]
    vars:
      test_cmd: ./gradlew test
      docs_required: docs/CHANGELOG.md
      union_merge_files: docs/CHANGELOG.md   # merges keep both sides' entries
      agent_env: JAVA_HOME=/path/to/jdk      # extra environment for the coding agents
      develop_branch: develop
      risk_threshold: "75"         # plans scoring above this wait for /approve
  - id: webshop-release
    source: schedule
    flow: release-daily            # daily tests + build on develop, PR develop → main
    github_repo: acme/webshop
    at: "17:00"
    timezone: Europe/Berlin
    task: Daily release pull request develop → main
OptionWhat it does
exclude_labelsNever touch issues with any of these labels
status_labelsYour own names for the status labels above
remove_on_doneLabels to remove when a run succeeds (e.g. the trigger label)
one_at_a_timeNever run two of this watcher's runs at once
wait_for_dependenciesOn by default: an issue with a Depends on / Blocked by section waits until those issues are closed (or have a dependency_done_labels label)
dependency_done_labelsLabels that also count as "done" for dependencies, e.g. Factory_done
precheck_flowRun this flow once over all new labelled issues before any is started (epic-questions asks every owner decision up front)
auto_defaultstrue: the watcher answers the Foundry's questions itself with the recommendations (it posts /defaults on the issue), so nothing waits for a person. At most twice per issue; if the planner still asks after that, a person answers. Default false
pause_while_pr_openStart nothing while a PR from a branch with this prefix is open (for the older two-label pipeline)
comment_on_failureOn by default: post the failure reason and output on the issue
status_commentOn by default: keep one status comment on every issue the watcher follows (see "The status comment")
ownerOnly for watchers in config.yaml: the e-mail of the account that owns the watcher's runs and may read and approve them. Empty: the first admin. It must be an account. A watcher of a repository uses the repository's owner

The issue list. Each issue watcher check reads the repository's issue list once: open and closed issues, newest first, 100 per GitHub call, at most 2,000 issues (20 calls). Watchers of the same repository share that read. The dependency check uses this list. The Foundry keeps the title, state, labels, links and dependencies of each issue, but no issue text, in the data folder (issue-lists/). If the list has more than 2,000 issues, the stored list is marked as cut and older issues are not seen by the dependency check. If the read fails, the check uses the previous way of reading open issues and logs the reason.

Coding agents run the build themselves. In the issue flows the coding agent may run the project's build and test commands (./gradlew, mvn, npm, pytest, go test, cargo, make) and read-only git commands — so it can check its own work and, for example, regenerate test fixtures. Pushing is never allowed. If the build needs environment settings (such as JAVA_HOME), put them in the agent_env variable: KEY=value pairs separated by ; or new lines. PATH, tokens and the Foundry's own variables can't be set this way. In a run that never uses the machine's login (see "What runs sign in with"), names that start with GH_, GITHUB_, GIT_, SSH_ or XDG_, and LC_ALL, are ignored too.

Bug stories go first#

A story with a bug label is repaired before the Foundry builds anything new. The watcher option priority_labels lists the labels (default [bug], not case sensitive). Set priority_labels: [] to turn it off for a watcher.

  • What goes first: in every check the watcher handles bug stories before all others: questions up front, starting, resuming and answering. When a slot is free, a waiting bug story gets it before any other story, also before stories that were queued earlier and stories of other repositories. Among bug stories the oldest issue goes first.
  • No per-check limit: bug stories are not counted against max_per_tick. Concurrency, the daily budget and one_at_a_time still limit what runs.
  • Code areas: when several stories wait for the same code area, a bug story gets it first when it becomes free. The other stories wait up to one check longer.
  • What a bug story still waits for: its own "Depends on" stories, the answers to its own questions, a daily budget or usage limit, and an open release pull request (pause_while_pr_open). It goes first after that.
  • Nothing running is stopped. A bug story only takes the next free slot.
  • Taking the label off ends it: the story goes back to its place in the queue.
  • Where you see it: a bug story has a "goes first" mark on the board and in the queue. Another story that waits for it says "waits: a bug story goes first".
  • Runs started by hand from the Runs page get no priority. Resume, approve and reject on the run page of a bug story do.

The status comment#

Every issue an issue watcher follows gets one status comment that says what happens next. It looks like this:

**Nothing needed from you** — it is being worked on.

Nothing to do, it continues by itself.

- **Where:** this issue

_This comment is kept up to date by the Spaghetti Code Foundry. It is edited, never posted again. Other comments are history._
  • Edited, never a second one. The Foundry changes the text of the same comment when the state changes. An edit sends no notification. Other comments (questions, plans, approvals, failures) are history and stay as they are.
  • When it is your move, the first line starts with What you need to do. Otherwise it says Nothing needed from you and why.
  • Done. Once the work is finished it says it is done. If the work waits for a scheduled release (a schedule watcher with at), it says it waits for that release, and says it is done after the release ran.
  • Closed while the run works. It asks you to cancel the run if the work is no longer wanted. When the run ends it says it is done.
  • No longer followed. If the issue is closed or loses the watcher's label, the last text says the Foundry no longer follows it (or that it is done, if its run succeeded). A closed-issue check that fails or is cut short changes nothing.
  • Two watchers on one issue (for example a plan and a code watcher) share one comment. It shows the record of the one that is working, else the one that waits for you.
  • Plain words. The comment uses the wording for a reader of the issue: no raw error text, folder, setting or amount of money. Pages of the Foundry app are named, not linked; the details are in the app.
  • Switch it off with status_comment: false on the watcher. A switched-off, removed or disabled watcher leaves its comments as they are.
  • Extra copies. If the Foundry finds more than one status comment of its own account on an issue, it keeps the oldest and removes the others (at most 5 per check; the rest follows at the next check).
  • A failure is only in the log. If a comment cannot be written, the check goes on; the log has a line status comment #<n>: … and the next check tries again.
  • After an upgrade, the first check adds one new comment (one notification) to every open issue the watcher follows, at most 30 per check. The Foundry remembers which issues have one in status-comments.json in its data folder (issue numbers only), so it can write a last text when an issue leaves the list, also after a restart.

Improve this page on GitHub