Spec to issues to shipped - how to keep planning and code in sync
Why trackers drift from reality, how to link issues to pull requests so status maintains itself, and the closed-loop workflow that keeps a spec truthful from first draft to deploy.
Mar 23, 2026 · 4 min read · Kash Gohil
On most teams, the issue tracker is a well-maintained work of fiction: "In progress" for work that merged Tuesday, "To do" for something half-built, a spec describing a system that no longer exists. This post is about closing the loop - linking specs to issues to pull requests so status maintains itself - and what that takes in any stack.
Why do trackers drift from reality?
Because status is manually duplicated state. The truth lives in git - branches, commits, merges - while the tracker holds a copy that a human must remember to update, usually after the interesting part is over. Any state maintained by memory decays; the only question is how fast.
Drift compounds through the chain. The spec says what was intended, the issues say what was planned, the code says what happened - three records, hand-synced at best. Standups, status docs, and "quick sync" meetings exist largely to reconcile them. That's the cost: not wrong data, but the human machinery of correcting it.
What does a closed loop look like?
Four links, each mechanical rather than remembered:
1. Spec → issues. Work items are derived from the approved spec, not retyped from it. Each issue references the section it implements, so "why does this issue exist?" always has an answer. (Structure your spec so this is easy - our RFC template is written with decomposition in mind.)
2. Issue → branch/PR. Every branch or PR declares which issue it implements - by convention (fix/123-login-timeout), magic words (closes #123), or explicit linking in the tool. This is the load-bearing link: it connects intent to change.
3. Merge → status. When the PR merges, the linked issue closes itself. No human in the loop. This one link eliminates the largest class of drift - done-but-not-marked-done. (The engineering behind that link is a closing-keyword parser we tested against 500 real pull requests - the naive version wrongly closes an issue mentioned in a code block.)
4. Status → spec. The originating document reflects live progress: which sections are built, in review, shipped. This is the rarest link - most stacks stop at #3 - and it's what keeps the spec a living record instead of a snapshot of intentions.
How do you build this in a typical stack?
With GitHub + Linear (or Jira), links 2 and 3 come from the integration: branch-naming conventions and closing keywords work well once the team actually uses them - so make the convention explicit and enforce it in review. Link 1 is manual discipline: write issues from the spec doc and paste back-references both ways. Link 4 is honestly not achievable automatically across Notion/Google Docs - the workable substitute is a status table at the top of the spec, updated at milestones, and accepting the lag.
The failure mode to watch: the sync covers status only. The discussion in chat, the review argument in the PR, and the decision that changed scope never reach the spec or the issue. Links carry state, not context - a structural limit of the multi-tool shape, which we compared honestly in Rezee vs Linear + GitHub.
How does Rezee close the loop natively?
Links 2 and 3 are structural rather than configured: issues link to branches and PRs in Code & Ship, and merges flow status back automatically, because they share one data model instead of two products and a webhook. A spec in Ideate & Design references its issues live, so reading the doc shows the current state of the work rather than the state on the day it was written. Chat mentions in Discuss unfurl live, so even the conversation stays attached.
Link 1 - deriving the issues from the spec in one step, each carrying its section reference - is the part we're building next, and until it lands it's the same manual discipline as anywhere else. We'd rather say that than describe it in the present tense.
The shared data model is also what makes delegation to agents safe: an agent working an issue can read the spec that argued for it, not just a title.
What conventions make it stick?
Whatever the tooling, the loop holds only if the team feeds it:
- No orphan branches. Every branch names its issue. Enforce in review, not by exhortation.
- No orphan issues. Every non-trivial issue names its spec (or explicitly "no spec - small fix").
- One issue per reviewable change, roughly. Giant issues spanning ten PRs blur status back into fiction.
- Close means merged, not "code written." Let the merge event define done, and the definition never argues.
FAQ
How do I link issues to pull requests automatically?
Use your platform's native mechanics: closing keywords in the PR description (closes #123 on GitHub), branch-name conventions that trackers like Linear detect (team-123-description), or explicit issue links. In Rezee, the link is a first-class field on the PR and the issue closes on merge without keywords.
Should issues close on merge or on deploy?
Merge, for most teams - it's the unambiguous, automatic event. If deploy lag matters to you, model it as a status ("Done" at merge, "Released" at deploy) rather than holding issues open, so the automatic link stays intact.
What about work that doesn't come from a spec?
Plenty of work is a bug report or a small improvement - forcing specs on it is ceremony. The rule that scales: issues above a size threshold (a day? a sprint?) trace to a written spec; everything smaller just needs the issue↔PR link.
Is a spreadsheet or standup enough to keep status in sync?
At two or three people, honestly, yes. The manual machinery breaks somewhere around five people or two concurrent projects - when reconciliation meetings start appearing, that's the signal the links should become mechanical.