This describes how we track and plan work across usegalaxy-be repositories. It applies org-wide (this file is the default for any repo that doesn't have its own).
All issues and PRs across the tracked repos (infrastructure-playbook, usegalaxy-be-tools, galaxytools, usegalaxy-be-doc, usegalaxy-be.github.io, infrastructure, pulsar-deployment, metrics_internal, issues) flow into two project boards:
issues is private and holds OKR-linked work with no ansible/infra component (outreach, admin, comms) - kept out of infrastructure-playbook on purpose, see the Objective section below.
- UseGalaxy.be Infrastructure (execution board): day-to-day and cycle-level tracking of individual issues and PRs.
- Compute Team Roadmap (strategic board): pillars, objectives, and epics (quarterly).
Most contributors only need the execution board. The roadmap board is for planning and reporting.
- Bug - something broken
- Feature - new capability
- Task - routine, scoped work
- Epic - a multi-part initiative, broken into sub-issues (use GitHub's native sub-issues, not a checklist inside the issue body)
If an issue is accumulating a checklist of more than 2-3 sub-parts inside its own body, that's a sign it should be an Epic with real sub-issues instead. Sub-issues get their own Status/Priority/Effort; the parent Epic shows a automatic completion status.
Epics don't get an Effort or an Iteration of their own. Each sub-issue gets its own Effort and is scheduled individually.
Epics do get their own Start/Target/End dates - Target and End are different things and both matter: Target is the estimate (a projection, revisable as things change), End is filled in only once the Epic is actually done, recording what really happened (automatically, from the close date). The gap between the two is useful information on its own (how far off was the estimate), not just planning noise.
Some Epics don't need a Target date set. We only set a real one when there's an actual driver (a leadership commitment, an external deadline, something else depending on it) - a rough estimate is fine, it doesn't need to be a hard commitment, but it needs some genuine basis. Don't set End before the Epic is genuinely finished.
Status: Backlog / In Progress / Blocked / Done. Kept deliberately small so it can be driven by automation (see below).
Priority: Urgent / High / Medium / Low. This is an org-level Issue Field (Settings > Planning > Issue Fields), not specific to this board - the same value is visible on the issue wherever it's tracked, not just here. Urgent is reserved for genuine escalations - never set automatically. As a starting default: goal-linked work (has an Objective) defaults to High, work tagged Not objective-linked defaults to Medium. Low is a manual downgrade for things that matter even less than that default, nothing defaults to it. Adjust as needed during triage.
Unlike Effort and Iteration, Priority applies to Epics too - it's about relative importance, not execution scheduling, so it isn't tied to fitting in one cycle. An Epic's Priority is what should drive which initiatives get staffed; a sub-issue's Priority is more about ordering work within an Epic that's already been deemed worth doing.
Effort: High / Medium / Low. Also an org-level Issue Field, same as Priority. Set at scheduling time (the Ready to schedule step below), not during triage - estimating effort for something that might sit untouched in Backlog for months is wasted precision, and it'll be stale by the time it's actually picked up. A single (non-Epic) issue should be scoped to fit inside one iteration (2 weeks) - not planned across two from the start. High effort is the signal it doesn't fit: convert it to an Epic and split it into sub-issues, not plan on rolling it into a second cycle. Rolling over is for the exceptional case where something unexpectedly slips, not a normal planning outcome - see the Start/Target date note below.
Things that predict a poor fit, worth checking before committing an item to an iteration:
- Can't describe "done" in one sentence (usually several issues bundled into one)
- Depends on someone outside the team acting first (VSC, Data Center, VIB, upstream) - their schedule bounds the timeline regardless of how small the actual work is
- It's "investigate/figure out X" rather than "do X" - split the investigation (done = a decision) from the fix (sized separately once scope is known), don't estimate both together
- Requires an RFC - same pattern as investigation, just heavier: split "write it and get it approved" (done = approved) from "implement the approved change" (sized once the RFC locks in scope/approach), don't estimate both as one item. The approval stage alone rarely fits one iteration on its own - writing takes at least half a day (more if it's a new type of RFC), then there's review turnaround (an external-dependency wait, same as above, just with internal reviewers), then revision, before scheduling can even happen. Risk level shapes how much of that time is realistic to expect.
- Real elapsed calendar time is involved beyond the work itself (deploy windows, a verification period, external review)
- No precedent - if nobody's done something like this before, lean toward a smaller time-box and reassess partway rather than a confident big estimate
When still unsure, call the effort lower rather than higher - there's already a graceful path for a wrong guess (mid-iteration Epic conversion, or the automatic Backlog bounce-back). A reasonable guess plus a working recovery path beats a perfect estimate upfront.
Iteration: 2-week cycles, Monday to Sunday. Represents "what cycle is this planned for," not a deadline. Only items actively planned for the current or next cycle should have one set.
Objective: which 2026 goal this work serves, if any - see the field's option descriptions on the project board for the full Key Result text under each Objective. Three states matter, and the difference between the first two is deliberate:
- Blank = not yet triaged
- Not objective-linked = triaged, confirmed this doesn't serve a stated objective - covers both spontaneous break-fix work and deliberately planned work (e.g. a scheduled upgrade) that just isn't tied to a 2026 goal. Both are expected, not a problem; this tag is about goal-linkage, not about whether the work was planned or matters.
- An Objective set = triaged, goal-linked
Objective is the only field this board uses for goal-linkage - the coarser Pillar grouping isn't tracked separately here, an Objective's own numbering (O1.x, O2.x, ...) already identifies which Pillar it belongs to.
A Key Result that's a concrete deliverable becomes an Epic tagged with that Objective - also add the OKR label and an [OKR] title prefix, for visibility outside the project board too.
These two markers work at different scopes, worth being deliberate about which one an issue gets:
Objectivefield: broad goal-linkage. Any issue that genuinely serves that Objective gets it, whether or not it's a headline deliverable - including ordinary supporting work that isn't itself a Key Result.OKRlabel /[OKR]prefix: narrow, reserved for the actual Key-Result-defining Epics only (~15-20 items). Not propagated to sub-issues or to other work that merely serves the same Objective - see the automation notes below for why.
An issue with Objective set but no OKR label is already a complete, correct state - "serves a goal, not itself the flagship deliverable." That doesn't need its own separate marker, it's directly visible from those two fields together.
Start date / Target date / End date: all three are org-level Issue Fields (Settings > Planning > Issue Fields), not project fields - the same value is visible on the issue wherever it's tracked, not just here. All three are pinned to show on every issue type.
For regular (non-Epic) issues, Start/Target are automatically derived from the Iteration's window and kept in sync. Epics are the exception - see Issue types above, their Start/Target/End are set independently rather than derived. When a regular issue gets a new Iteration and already has a Start date, only Target moves forward - Start is preserved so the Roadmap bar visibly stretches across iterations instead of quietly resetting to looking on-track every cycle. If Start is blank (the item never actually got started - see below), both Start and Target are set fresh to the new iteration's window.
End date is set automatically when an item reaches Status = Done, from the issue's own close date, so it records when the work actually finished rather than which cycle it was planned for. It is never overwritten: a date already filled in by hand stays, including the far-future bound a long-running OKR Epic may carry while it is still in progress. A reopened item keeps its old End date until someone clears it by hand.
Use the existing weekly Monday meeting, in two different modes depending on where it falls in the 2-week cycle. Both modes share one rule: triage (classification) always happens before scheduling. An item isn't a scheduling candidate until it's triaged, whether that happened just now or weeks ago.
Iteration-boundary Monday (every other week): two classification passes over disjoint sets of items, then one shared scheduling pass that doesn't care which pass an item came from.
- Triage new issues: the board's Triage view (anything missing label/assignee/Objective/Type/Status). "New" means untriaged, not necessarily created since the last meeting - an old item that slipped through counts too. For each, set Objective and Priority (or mark it Not objective-linked), confirm the Type is right, add labels as applicable. Effort is not set here, see the Effort field notes above for why. Pre-dates the templates, or was filed without one? See docs/retroactive-triage.md for a copy-paste comment block that gives it the same structure. If an issue genuinely can't be classified as written, mark it
needs-clarificationinstead of guessing at field values to move it along - it stays visible in Triage either way, the label just means whoever reopens it next doesn't have to re-diagnose it from scratch. - Re-triage leftover issues: the board's Needs Re-triage view (items the automation bounced back to Backlog because their iteration ended while they were still open). Still the priority? It's already triaged from before, nothing more to do, it's ready for step 3. Turned out bigger or different than expected? Adjust Effort/Type, rewrite the description, split into sub-issues if it's really an Epic. Not right now? Leave it - it'll keep resurfacing here every cycle until it's either scheduled or someone clears the
needs-retriagelabel by hand, and an item that keeps bouncing is itself worth noticing. - Schedule: the board's Ready to schedule view, grouped by Priority and sorted by Effort. Covers everything triaged in steps 1 and 2 together - by this point they're indistinguishable, both just triaged Backlog work competing on the same criteria. Pull top items into the now-starting iteration up to the In Progress column's limit (a soft cap, see column settings); stage a few likely candidates into "next" as a non-binding preview, re-reviewed for real (not rubber-stamped) at the following boundary. This is issue-level and one iteration ahead only; picking which Objectives matter this quarter is a separate, coarser decision made at quarterly planning, not here.
- Epic check-in: anything the "no sub-issues after 2 weeks" nudge has flagged, anything carrying
roadmap-drift(see Roadmap drift below), or any Epic close to fully rolled up. Also check the Active OKRs view - it has its own WIP limit (separate from the execution board's) on how many Epics can be Status=In Progress at once. Blocked Epics free a slot; pull in the next-highest-priority in-focus Epic from Backlog, don't just start more on top. The point is to actually finish Epics rather than spread thin across many at once.
Mid-iteration Monday (business as usual, no scheduling decisions):
- Triage new issues, same as step 1 above, so the backlog doesn't pile up untriaged between boundaries.
- A light health check on the running iteration: anything Blocked that needs help unblocking? Anything clearly not going to land, worth flagging (and maybe converting to an Epic) now rather than waiting for the automatic bounce-back at the boundary?
- Epic check-in, same as step 4 above - Epic progress isn't tied to the 2-week cycle the way regular items are, so it's worth a glance here too, not only at the boundary.
- Optionally, a quick sanity-check on whatever's staged as "next" from the last boundary, only if something's materially changed. Not a full re-review, that's what the following boundary's step 3 is for.
What doesn't happen mid-iteration: no re-scoping the running iteration, no pulling new items into it, no re-litigating what's already committed. That's what keeps the 2-week commitment mean something rather than a moving target.
Only genuinely urgent work (something's broken or blocking users right now) skips triage and moves straight from Backlog to In Progress. Everything else goes through iteration planning, whether or not it's goal-linked - a deliberately scheduled upgrade or migration is triaged, sized, and scheduled into a cycle the same as goal-linked work, even though it'll end up tagged Not objective-linked. That tag means "doesn't serve a stated objective," not "wasn't planned" or "isn't important."
There's a third type of work besides "urgent, skips the queue" and "goal-linked": small, cheap, non-urgent, non-goal-linked work (a stale doc update, ...) that will always be low priority compared to anything else and would otherwise sit in Backlog forever. Rather than forcing these through Effort/Iteration scheduling they have no real timing need, mark them with the opportunistic label: picked up whenever someone has spare capacity, with no formal commitment to when and no Iteration assigned at all. If something's sat untouched for a long time even as "opportunistic" and nobody's ever picked it up, that could be a sign to close it rather than keeping it. The room for this comes from how the item limit in an iteration. Keep iteration planning deliberately below the team's full theoretical capacity.
Sequencing goal-linked work is decided at quarterly roadmap review, at the Epic level, not the Objective level - see docs/quarterly-planning.md. Only Epics picked as in-focus get pulled into iterations; the rest stay tagged and visible on the roadmap.
When an item is still open at the end of its iteration, this is handled automatically rather than left for someone to remember: it's moved back to Backlog and its Iteration is cleared, with a comment explaining why. Staying "in iteration" would implicitly claim it's still what's being worked on; going back to Backlog forces a fresh re-check at the next triage instead of assuming continuity. If it was In Progress, Start/Target are left as-is (a real trace that work was in flight); if it never actually started, Start/Target are cleared too since there's nothing to preserve.
What's still a human call at that point: if it turned out bigger than expected, split it into an Epic instead of re-entering it as-is. Otherwise, decide at the next triage whether it's still the priority (pull it into the new iteration) or not (leave it in Backlog). An item that keeps bouncing back cycle after cycle is itself worth noticing - either it isn't the priority its label says, or capacity is being chronically eaten by work this board doesn't track at all.
The unplanned label marks anything worked on during an iteration that wasn't committed to that iteration at its boundary. It records how the work arrived, nothing else:
- It says nothing about urgency. An incident that took the service down and a small item picked up because the cycle happened to have room both get the same label.
- It says nothing about importance or goal-linkage. Unplanned work can be any Priority, objective-linked or not.
- It isn't a criticism. Some unplanned work every cycle is normal, and the plan already assumes it.
- It doesn't apply to Epics. Epics never get an Iteration in the first place (see Issue types above), so there's no commitment for them to fall outside of. This label is about iteration capacity, and only non-Epic items are scheduled that way.
It's applied automatically, when the item moves to In Progress rather than when it's filed. An issue that arrives mid-cycle and waits in Backlog for the next boundary is never unplanned - it got scheduled the normal way, just later. The distinction is commitment, not filing date.
An opportunistic item that actually gets picked up during a cycle gets unplanned too. The two answer different questions: opportunistic is how the item is allowed to be scheduled (no Iteration, spare capacity only), unplanned is the fact that real capacity went to it outside what was committed.
Why track it at all: docs/quarterly-planning.md weighs what to take on against how much capacity realistically goes to unplanned work rather than theoretical full capacity. Without the label that share is a guess. Filtering an iteration by label:unplanned turns it into a number, and a cycle where most of the work carries it is worth discussing on its own.
The roadmap and the iterations are two views of the same work, so they should agree: if a sub-issue is being worked on now, the Epic above it should be an initiative that is actually active - Status = In Progress, and inside its own Start/Target window. When they don't agree, the Roadmap view draws a bar that has nothing to do with what the team is doing, and an Epic that shows as finished (or not yet started) keeps accumulating work underneath it.
The roadmap-drift label marks an Epic where that has happened. It's applied automatically to an Epic with live work under it (the Epic itself In Progress, or any of its descendants) when any of these is true:
- the Epic isn't In Progress while work under it is
- the Epic has no Start date
- the Epic's Start date is later than the start of the work under it
- the Epic's Target date is earlier than the target of the work under it
No Target date at all isn't drift - an Epic without a real driver is allowed to carry none (see Issue types above).
The Epic's dates are never rewritten by the automation. A window is a human judgement, and the right fix depends on which side is wrong: move the Epic's Target because the initiative genuinely runs longer, or stop pulling sub-issues into an Epic that was supposed to be finished. Unlike unplanned, this label is a live state and not a record - it's removed again as soon as the two agree, so what's labelled is always a current disagreement.
Merging is not the same as done. Most of this work deploys via a separate playbook run, a merged PR just means the code is in main, not that it's live. Status=Done only happens when the issue is actually closed, and that's a decision for whoever verifies the deploy, not something that fires automatically on merge.
How you reference the issue in the PR depends on which is true for that repo/change:
- Merge effectively is deploy (e.g. a docs site that publishes on merge): use
Closes #123. GitHub auto-closes the issue when this PR merges, which then triggers Status=Done. Fine as-is. - Merge implies a manual deploy step (most repos, most of the time): use
Relates to #123instead. This links the PR to the issue for traceability without auto-closing anything. Close the issue by hand once the deploy is verified.
This also settles what to do about multiple PRs on one issue: since closing is decoupled from any individual PR merging, it doesn't matter how many PRs are linked or in what order they merge - closing is still one deliberate action once the work is actually live.
Multiple PRs on one issue isn't automatically a sign it should have been split, some changes can need PRs in more than one repo, and follow-up/fix PRs are normal. Worth a second look during the Epic check-in only if there are several PRs and none of them look like an obvious cross-repo split.
Automated (see the workflows in this repo, and the board's own Settings > Workflows):
- New issues/PRs are added to the execution board with Status=Backlog
- Closing an issue sets Status=Done (merging a linked PR does not, by itself - see Pull requests above)
- Reopening an issue resets Status=Backlog
- A
blockedlabel mirrors to Status=Blocked (and clears when the label is removed) - Items still open when their iteration ends are moved back to Backlog with Iteration cleared (Start/Target handled per the rules above), with a comment explaining what happened
- Start/Target dates are kept in sync with Iteration whenever it changes
- End date is set from the issue's close date once an item reaches Status = Done, whatever iteration it ended up in. Never overwritten, so a hand-entered date wins
- Objective is kept in sync from a parent issue down to its sub-issues, and recursively on down to sub-sub-issues and beyond in the same run - the parent is authoritative, so this overwrites a stale or mismatched Objective, not just blanks. Mark an item
Not objective-linkedif it should genuinely be exempt from its parent's goal-linkage, that's the one value inheritance never overrides. The OKR label and[OKR]title prefix are not propagated - those mark the top-level Objective-linked issue only, not every sub-issue under it. Polls every 30 min rather than reacting instantly (sub_issuesis a webhook event, not a valid Actions trigger) - only as accurate as the underlying parent/child links, worth spot-checking those. Scheduled runs aren't fully reliable (GitHub can silently drop a queued cron run under load), worth a manual trigger if something looks stale - Items entering In Progress with no Effort set get a nudge comment
- Non-Epic items worked on inside an iteration they weren't committed to get the
unplannedlabel, either because their Iteration was set after the boundary or because they have none at all. Only counts items that entered In Progress during that cycle, so a card left sitting in the column from an earlier one isn't swept up. Never removed once applied - it's a record, not a state - Epic-typed issues with no sub-issues after some time get a nudge comment
- Epics whose own Start/Target window or Status no longer matches the work running under them get the
roadmap-driftlabel, removed again once they line up. The Epic's dates are never changed automatically
Manual, by design:
- Setting Priority above the High/Medium defaults
- Setting Objective
- Deciding an item is Not objective-linked rather than just untriaged
- Deciding whether an item bumped back to Backlog is still the priority (re-enter it) or not (leave it)
- Judging whether a cycle's share of
unplannedwork is acceptable, and what to do about it - Deciding a stuck item should become an Epic instead of being re-entered as-is
- Setting/revisiting an Epic's Start/Target dates, and clearing an End date that a reopen made wrong
- Resolving a
roadmap-driftflag: deciding whether the Epic's window should move, or whether the work under it shouldn't be running yet - Closing an issue once its deploy is verified (for anything not using
Closes #123)
See docs/references.md for the non-GitHub-specific methodology this is based on. New issues use the org-wide Task/Bug/Feature or Epic templates by default (.github/ISSUE_TEMPLATE/), which bake in the iteration-fit checklist and Epic conventions above.