---
title: 'Issue Tracking and Boards'
source: 'https://academia.sh/en/courses/code-review/issue-tracking-and-boards'
course: 'Code Review and Team Process'
language: en
updated: '2026-08-17T18:10:46+00:00'
license: 'CC BY-SA 4.0'
---

# Issue Tracking and Boards

The wait a work item spends between statuses is 19 units outside review; the review round varies between 12 and 36 depending on the assignment and its share climbs from 0.387 to 0.655; putting three remaining defects back on the board takes the total from 31 to 105.

Up to this point, review was measured around a single change: how many axes were looked at,
how many chunks were read, how many rounds it went through. The change stood alone in these
measurements — it came from nowhere and went nowhere. In a team, though, a change is not
just a change; **it is one item inside a flow.** It is born as a proposal, enters a queue,
someone takes it on, it is written, reviewed, merged, and closed.

In this chain, review is only one link. This lesson's question is: **how long does the link
itself take, and what is its share of the whole chain?** To know the value of shortening a
review round, it first has to be known what the round stands inside of. If the rest of the
chain is longer than review, cutting the review round in half changes little in the total.

## The Work Item and Its Statuses

A **work item** is the smallest unit of work a team tracks: a bug report, a request, a
task. An item has a single identity, a single owner, and at any moment a single
**status**. A **board** is the surface these items are laid out on according to their
status; every column is a status, and an item moves left to right.

A board is not a record tool, it is a **visibility** tool. The same information could stand
in a list too; what a board adds is that how many items have piled up in which status can
be read at a glance. A column swelling says that exit from that status is slower than entry
into it.

```text
# example board dump — not executed

proposal      queued        in progress   in review     merged
──────────    ──────────    ──────────    ──────────    ──────────
#412          #398          #377          #361          #350
#411          #395          #374          #359
              #391                        #356
              #388                        #352
                                          #349
```

The status set itself is a design decision, and it has a single criterion: **a status
makes sense when its exit depends on someone else's action.** `in review` is a status
because there the item is waiting not on the author's action but the reviewer's. A column
whose exit is always in the same person's hands is not a status, it is that person's
personal list; it takes up room on the board and reports no wait. The same criterion
limits column count too: a new status is added only if there is a new handoff point.

In this dump, the `in review` column carries five items, `in progress` two. The number
itself is not a defect; the defect is the column's **emptying speed** falling below its
filling speed. If five items have piled up there, the **review queue** is lengthening, and
a lengthening queue writes wait onto every one of them.

## Wait Stands Not in a Status but in a Transition

This is the point where a board readily misleads. Not all of the time an item spends
standing in the `in review` column is review; part of it is **waiting for the reviewer to
look at the item.** What has to be measured is not how long the item stands in one column,
but how long it takes to **move** from one column to the next.

This distinction defines the measurement. There are five transitions in the chain, and
four of them are outside review: a proposal enters the queue, someone from the queue takes
it on, writing finishes and the item goes out to review, review finishes and the item
closes. The fifth transition — from `in review` status to `merged` status — is review
itself and it is **not fixed**: it depends on how many rounds it goes through.

**Wait time** is counted here without a unit. Not hours or days, but a comparable unit;
the goal is not to read the absolute duration but the ratio between links to each other.
These metrics are metrics **of the process**: how long an item waits is the result not of
the person looking at that item, but of the shape of the chain.

## Column Length and Wait Are Not the Same Thing

The five items standing in the board's `in review` column are an **accumulation**; the
wait measured at the transition is a **rate**. The two do not give the same number. A
five-item column means one unit of wait if the exit rate is five items per unit; it means
five units of wait if the exit rate is one item per unit. Someone looking at the column
cannot tell the two states apart, because a board shows accumulation and does not show
rate.

The direct consequence of this is: **shortening the column does not shorten the wait.**
Moving two of the five items to another column lowers the accumulation and relieves the
board; it does not touch how long the transition takes. The only thing that shortens the
wait is speeding up the transition itself, and in the review link this means lowering the
round count.

One more distinction is needed. A single work item can carry more than one change; then
the review link runs more than once per item, and the chain's total grows accordingly.
This measurement counts **one** change per item and gives its numbers under that
assumption. If an item is split into three changes, the transitions outside review are
paid once, the review link three times — its share grows in that proportion. This says
that splitting items is not free: splitting makes review easier but raises review's share
of the chain.

## Review's Place in the Chain

What determines the length of the review link? The course's fifth reading already said
this: **what lengthens the round and the wait is what could not be seen in the first
round.** What is found in a round gets fixed; what is not found carries over to the next
round, and every round writes one more wait onto the chain. If axis diversity is low, the
first round finds little, round count rises, the link lengthens.

The chain's total is therefore fed from two sources. One is the **fixed** part — the
proposal, queue, writing, and closing transitions; these are independent of how review is
done. The other is the **variable** part, and it is the direct result of axis assignment.
The measurement counts these two separately and gives their ratio.

There is one more defect: the **remaining defect** once the rounds are over. A remaining
defect does not mean review is finished and the item is closed; the closed item later gets
**reopened** because of that defect and enters the chain a second time. This second pass
does not start with the proposal — the item has already been proposed — but it repeats
everything starting from the queue.

The measurement's assumptions:

- **TF1** — The shared setup's 600-line change and 24 defects are used as they are; the
  class distribution and the oracle are the course's constant.
- **TF2** — The wait of the four non-review transitions is fixed and independent of the
  assignment: **5**, **9**, **2**, and **3** units respectively. These values represent
  the chain's shape, not a team's measurement.
- **TF3** — The review link's wait comes from the shared setup's round cycle: every round
  writes **6 units** and round count is the result of the defect count found in the first
  round.
- **TF4** — A found defect is considered fixed in that round; a remaining defect carries
  over to the next round. Six rounds is the upper bound, and an assignment resting on that
  bound leaves a defect remaining.
- **TF5** — A remaining defect reopens the item **once** after it closes. The second pass
  starts from the `queued` status; the proposal transition is not repeated.
- **TF6** — The metric is reported per item, not per person. No number in the chain can be
  read as a role's performance; all of it is the result of the chain's shape.
- **TF7** — The set's resolution at 24 defects is **1/24**; because the wait unit is **6**
  per round, the smallest measurable difference in the chain is **6** units.

## Measurement

```python
"""Work item chain: wait between statuses and review's share.

Part 1 - found, missed, and review wait in four assignments.
Part 2 - the chain's total, review's share, and the cost of reopening.
"""
SEED = 20260815
AXES = ("interface", "implementation", "test", "documentation", "style")
UNWRITTEN = "unwritten requirement"
CLASSES = AXES + (UNWRITTEN,)
ATTENTION, CHUNK = 12, 50
# Transitions outside review: wait read off the board.
OUTSIDE = (("proposal", "queued", 5), ("queued", "in progress", 9),
           ("in progress", "in review", 2), ("merged", "closed", 3))


def rng(seed):
    d = seed % 2147483646 + 1

    def draw(n):
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return draw


def change(lines, defect_count=24, seed=SEED):
    draw, defects = rng(seed), []
    chunk_count = max(1, lines // CHUNK)
    for i in range(defect_count):
        defects.append({"no": i + 1, "class": CLASSES[draw(6)],
                         "chunk": draw(chunk_count)})
    return {"lines": lines, "chunks": chunk_count, "defects": defects}


def review(d, axes, attention=ATTENTION):
    read = set(range(min(attention, d["chunks"])))
    return {k["no"] for k in d["defects"]
            if k["class"] in axes and k["chunk"] in read}


def panel(d, assignments, attention=ATTENTION):
    found = set()
    for axes in assignments:
        found |= review(d, axes, attention)
    return found


def review_rounds(found, defect_count, wait_per_round=6):
    remaining, rounds, wait = defect_count - len(found), 1, wait_per_round
    while remaining > 0 and rounds < 6:
        rounds += 1
        wait += wait_per_round
        remaining -= max(1, len(found) // 2)
    return rounds, wait, max(0, remaining)


FULL = set(AXES)
ASSIGNMENTS = {
    "single reviewer, all axes": [FULL],
    "three reviewers, same axis": [{"implementation"}] * 3,
    "three reviewers, separate axes": [{"interface"}, {"implementation"},
                                        {"test", "documentation"}],
    "five reviewers, five axes": [{a} for a in AXES],
}
OUTSIDE_TOTAL = sum(b for _, _, b in OUTSIDE)
d = change(600)

print(f"work item 1 | change {d['lines']} lines, {d['chunks']} chunks, "
      f"defects {len(d['defects'])} | wait outside review {OUTSIDE_TOTAL}")
print("transitions outside review: "
      + ", ".join(f"{a}→{b} {n}" for a, b, n in OUTSIDE))
print()
print(f"{'assignment':<32s} {'found':>7s} {'missed':>5s} {'unwritten':>10s} "
      f"{'rounds':>3s} {'review':>8s} {'remaining':>5s}")
for name, assignment in ASSIGNMENTS.items():
    b = panel(d, assignment)
    missed = [k for k in d["defects"] if k["no"] not in b]
    unwritten = sum(1 for k in missed if k["class"] == UNWRITTEN)
    rounds, wait, remaining = review_rounds(b, len(d["defects"]))
    print(f"{name:<32s} {len(b):7d} {len(missed):5d} {unwritten:10d} "
          f"{rounds:3d} {wait:8d} {remaining:5d}")

print()
print(f"{'assignment':<32s} {'chain':>6s} {'review share':>13s} "
      f"{'reopening':>14s} {'total':>6s}")
for name, assignment in ASSIGNMENTS.items():
    b = panel(d, assignment)
    rounds, wait, remaining = review_rounds(b, len(d["defects"]))
    chain = OUTSIDE_TOTAL + wait
    # A remaining defect puts the item back on the board: a second round starting from queued.
    reopened = 0 if remaining == 0 else chain - OUTSIDE[0][2]
    print(f"{name:<32s} {chain:6d} {wait / chain:13.3f} "
          f"{reopened:14d} {chain + reopened:6d}")
```

```
work item 1 | change 600 lines, 12 chunks, defects 24 | wait outside review 19
transitions outside review: proposal→queued 5, queued→in progress 9, in progress→in review 2, merged→closed 3

assignment                         found missed  unwritten rounds   review remaining
single reviewer, all axes             22     2          2   2       12     0
three reviewers, same axis             6    18          2   6       36     3
three reviewers, separate axes        15     9          2   3       18     0
five reviewers, five axes             22     2          2   2       12     0

assignment                        chain  review share      reopening  total
single reviewer, all axes            31         0.387              0     31
three reviewers, same axis           55         0.655             50    105
three reviewers, separate axes       37         0.486              0     37
five reviewers, five axes            31         0.387              0     31
```

## Reading the Chain

The wait outside review is **19** units, and it is the same in all four rows. The only
thing that changes is the review link, and it swings between **12** and **36**. The
chain's total accordingly swings between **31** and **55**: the worst assignment produces
a chain **77%** longer than the best one.

Review's share is in the right column. In the best assignment, **0.387** of the chain is
review; that is, roughly two-thirds of the item's wait happens outside review. In the
worst assignment, the share climbs to **0.655** — review is now more than half the chain.
The gap between these two numbers gives a result: **review is not a fixed-cost link, it is
the chain's most volatile link.**

A practical ordering follows from this. The `queued→in progress` transition is the
longest non-review link at **9** units, and in the good assignment it alone is about
three-quarters of the review link. In the chain's best state, the place to shorten is not
review, it is the queue. But in the bad assignment, review is **36** units — four times
the queue — and there, shortening the queue contributes little to the total. **Which link
to shorten depends on which assignment it is**, and this is told not by the column length
on the board but by the transition's wait.

The table on the left also gives found and missed for the same rows, and the two should be
read together. The **three reviewers, same axis** row finds **6** and misses **18**; the
row with the longest chain is also the row that finds the least. **Single reviewer, all
axes** finds **22**, misses **2**, and has the shortest chain. So a long chain does not buy
a better review — **the source of the length is exactly what could not be found.**

Among the missed, every row has **2** **unwritten requirement** defects, and this number
does not change across the four rows. Neither the chain's shape, nor the assignment's
diversity, nor round count finds those two defects. Issue tracking is not the remedy for
this class; however well an item is tracked on a board, the absence of what was never
submitted is invisible to review.

## Reopening

The second table's last two columns give the chain's real cost. In the **three reviewers,
same axis** assignment, **3** defects still stand after six rounds. The item is closed;
the defects are not. A remaining defect eventually surfaces somewhere and the item is
**reopened**.

Reopening adds **50** units to the chain: every transition besides the proposal runs one
more time. The total climbs from **55** to **105**. The same change closes in **31** units
in the best assignment; the gap here is **74** units, more than three times as much.

How this number is read matters. Reopening is not a delay, it is a **deferred wait**: the
wait not paid on the chain's first pass is paid with interest on the second pass. Someone
looking at the board sees no problem on the first pass — the item is closed, the column is
empty, the numbers are clean. The problem appears on the board when the item comes back,
and at that moment it is no longer associated with the original review's measurement.

This is why a closed-item count alone is not enough to make a board readable. **The number
of reopened items has to stand as its own column or its own count;** otherwise the
chain's most expensive behavior is invisible anywhere. A closing count reports two
different processes as the same number for as long as it does not separate what closed on
the first pass from what closed on the second.

## Summary

- A **work item** is the smallest unit of work in a flow; a **board** lays items out by
  status, and wait accumulates not in the statuses but in the **transitions** between
  them.
- The chain's wait outside review is fixed at **19** units; the review link varies between
  **12** and **36** depending on the assignment, carrying the chain's total from **31**
  to **55**.
- Review's share of the chain is between **0.387** and **0.655**; review is not a
  fixed-cost link, it is the chain's most volatile link, and which link to shorten
  depends on the assignment.
- A long chain does not buy a better review: the **36**-unit link finds **6** defects and
  misses **18**, the **12**-unit link finds **22** and misses **2**.
- The **3** defects remaining after six rounds reopen the item and add **50** more units
  to the chain; the total climbs to **105**. A closing count hides this second pass unless
  it is counted separately.
- In all four of the four assignments, **2** **unwritten requirement** defects are missed;
  issue tracking does not find this class.

## Next Step

This measurement assumed the item's author knew what to submit: the change entering the
chain was a change written while trying to meet the team's expectations. So where does the
author learn those expectations from? Someone inside the team has learned them across
rounds; someone coming from outside has not. An unwritten expectation comes back as a
finding in review's first round, and every finding writes one more round. The next lesson
measures the written form of these expectations: which classes does a **contribution
guide** close before review, how many rounds does closing each class cut, and is it really
the guide that is the one mechanism able to touch a class no axis searches for?
