← All workflow playbooks

ProductBoard vs Claude Code for Feature Prioritization, Step by Step

Date: July 6, 2026 • Author: Second Axis

You're the PM staring at a ProductBoard renewal quote, or evaluating it for the first time, and asking the 2026 question: do I still need a dedicated prioritization tool, or can I build this on Claude Code? The comparison starts with what each thing does. ProductBoard and tools like it are mature products: polished UI, a customer portal, integrations out of the box. But what they fundamentally do is organize what humans type into them. When a column says "Reach: 4,000 users," a person estimated that number and typed it in, and nobody can tell you which query it came from, because there wasn't one.

A Claude Code build inverts the trade. Reach comes from a real analytics query it ran itself. Every score cell links to its evidence. Dedupe happens with embeddings instead of a PM squinting at two similarly worded cards. The price is that you build and maintain all of it. This post is the entire build, so you can price both sides accurately.


What you're building

Every Monday morning, refreshed without you touching anything:

  • A ranked prioritization table of your active initiatives, scored with RICE, where every cell is a link: Reach links to the analytics query that produced the number, Impact links to the evidence documents, Confidence shows the source count behind it, Effort links to the engineering estimate.
  • A problem registry with stable IDs, so "PROB-041: exports lose formatting" is the same row this week, last week, and in October, and its score has a trend line.
  • A judge-verified refresh: an agent that challenges every score before it ships. Reach says 4,000 users? Show the query. No query, no score.
  • An override layer: when you rank something above its score for strategic reasons, that's recorded as data with a reason, visible in the table, never a silent edit.
  • A Slack summary of rank changes only, because "nothing moved" is a one-line message, not a report.

The scale math (why this needs architecture)

Feature requests arrive scattered and rephrased. A typical B2B product with a real sales motion sees 200-400 raw requests a week across four channels: sales call notes ("prospect asked twice about SSO"), support tickets, the community Slack, and exec asks. The same problem shows up as "CSV export is broken," "downloads don't match the dashboard," and "customer says reports are wrong." Volume-wise this is small; the hard part is fragmentation and duplication.

The scoring side is where the query count adds up. Say the registry holds 40 active initiatives, each needing 4 evidence queries per refresh: Reach against analytics, Impact from linked evidence, Confidence over sources, Effort from the eng tracker. That's 160 evidence-gathering operations per weekly run. One session doing them serially is slow, and worse, 40 initiatives' worth of query results stacked into one context turns the tail of the run into mush. The fix is the subagent fan-out: one scoring agent per initiative, each with its own clean context, running its 4 queries and returning one small scored record. 40 initiatives means 40 fan-outs, and this is precisely the workload subagents exist for.


Phase 0: Prerequisites

  • Claude Code installed and authenticated, on a Pro, Max, Team, or Enterprise plan for the scheduling and publishing steps.
  • Analytics access for Reach. PostHog's official MCP server is the path we wire below; if your usage data lives in a warehouse instead, Google's MCP Toolbox for Databases covers BigQuery and Postgres with a read-only service account.
  • Eng tracker access for Effort. Linear's official MCP server, or Atlassian's official MCP server if you're on Jira.
  • Your intake sources. Wherever call notes, tickets, and Slack threads land. Exports or APIs for each; specifics below.
  • Python 3.11+ with an embeddings option (a local sentence-transformers model is free and plenty for this volume).
  • A dedicated git repo. The registry's stable IDs and the weekly refresh both depend on committed history.
  • Optional scaffolding: Anthropic's Product Management plugin from the knowledge-work-plugins repo ships /synthesize-research and /roadmap-update commands. Useful, but it does not contain the registry, the scoring fan-out, or the judge below. That's this playbook.

Phase 1: The repo and the registry

feature-prioritization/
├── CLAUDE.md
├── .mcp.json                     # posthog + linear, team-shared
├── .claude/
│   ├── agents/
│   │   ├── initiative-scorer.md  # scores ONE initiative
│   │   └── judge.md              # challenges every score
│   └── skills/
│       └── rice-refresh/
│           └── SKILL.md
├── scripts/
│   ├── intake_extract.py         # per-source extractors → request records
│   └── dedupe_assign.py          # embed + assign to registry problems
├── registry/                     # committed: the system's memory
│   ├── problems.json             # stable problem IDs, evidence links
│   └── overrides.json            # PM strategic overrides, with reasons
├── data/                         # gitignored: raw notes, tickets, embeddings
└── runs/                         # committed: weekly scored tables
    └── 2026-07-06/
        ├── scores/               # one JSON per initiative, with evidence
        └── ranked-table.md

registry/problems.json is the load-bearing file. Each entry has a stable ID, a canonical problem statement, an embedding centroid, and links to every raw request that mapped to it. IDs never get renumbered; when problems merge or split (they will), the registry records a mapping, exactly like cluster identity in the text-at-scale playbooks. Stable IDs are what make "this problem's Reach doubled since May" a sentence you can say with a straight line behind it.

CLAUDE.md carries the rules every session inherits, scheduled ones included:

## Registry rules
- Problem IDs in registry/problems.json are STABLE. Never renumber.
- Merges/splits write an old-ID → new-ID mapping; reports use it.
- Every score component must carry an evidence link. A score
  without evidence is a bug, not an estimate.
- overrides.json is data. Never edit agent-produced scores to
  match an override; the table shows both.

Phase 2: Intake and dedupe

intake_extract.py runs one extractor per source, each turning raw text into a small structured record: source, date, requester segment, the problem in the requester's words, and a verbatim quote. This is the same map-pass machinery as the conversation intelligence playbook, pointed at four smaller streams, so we won't re-derive it here: cheapest model that passes your spot-checks, verbatim-quote flag, versioned schema. At a few hundred requests a week the volumes are two orders of magnitude smaller than mining chat logs, which matters for the cost section later.

dedupe_assign.py embeds each new request's problem statement and compares it to the registry's centroids. Within threshold, the request attaches to the existing problem and its evidence list grows. Beyond threshold, it's held out; when holdouts form a dense group, a candidate problem is proposed with a provisional ID. A human confirms the canonical wording before it enters scoring, via a one-line PR to problems.json. That step stays manual on purpose: naming the problem is product judgment, not geometry.

The payoff is the thing prioritization tools struggle with most: "SSO," "SAML support," and "IT can't approve this without single sign on" become one problem with three evidence sources, not three cards splitting one vote.


Phase 3: The scoring engine

Each active problem the team commits to investigating becomes an initiative, and each initiative gets scored by its own subagent.

.claude/agents/initiative-scorer.md

---
name: initiative-scorer
description: Produces an evidenced RICE score for ONE initiative
tools: Read, Bash
model: inherit
---
You score exactly ONE initiative, given its problem ID.
Run the four evidence queries, in parallel where possible:

R - REACH: query analytics (PostHog MCP) for users affected in
    the last 90 days. Record the exact query text and result.
    No query = no Reach score.
I - IMPACT: read the evidence files linked in problems.json.
    Score 0.25 to 3 per RICE convention; justify from evidence.
C - CONFIDENCE: compute, don't vibe: source count, source
    diversity (how many of the 4 channels), requester spread.
    3 sources in 1 channel < 3 sources across 3 channels.
E - EFFORT: fetch the current estimate from Linear via MCP.
    If none exists, output "UNESTIMATED", never a guess.

Return one JSON record: four components, each with its
evidence link, plus the computed RICE score.

The skill orchestrates the fan-out:

.claude/skills/rice-refresh/SKILL.md

---
name: rice-refresh
description: Weekly evidenced RICE refresh of all active initiatives
---
1. PRIOR STATE: read the latest runs/*/scores/ and
   registry/problems.json. Note last week's ranks.
2. INTAKE: run intake_extract.py then dedupe_assign.py.
   List candidate problems for human confirmation; do not score them.
3. FAN OUT: one initiative-scorer subagent per active initiative,
   in parallel. Pass each its problem ID and last week's score record.
4. JUDGE: run the judge on every scored record. Rejected scores
   go back to their scorer once; still failing = "UNSCORED" with
   the judge's note, never a filled-in guess.
5. RANK: build ranked-table.md. Apply overrides.json as a separate
   displayed column with reasons, not as score edits.
6. PERSIST: commit scores/, the table, and any rank-change summary.
7. SHARE: publish the table; post rank CHANGES to Slack.

The judge is what separates this from a spreadsheet with extra steps:

---
name: judge
description: Challenges every RICE component before it ships
tools: Read, Bash
---
For each scored record:
1. Reach: does the record contain the literal query? Re-run it.
   Result differs by >10% or query missing → REJECT.
2. Impact: does the justification quote evidence that exists in
   the linked files? Spot-check the quotes.
3. Confidence: recompute from the evidence list. Mismatch → REJECT.
4. Effort: does the Linear issue exist and carry that estimate
   now, not last month?
5. Any component whose evidence link 404s → REJECT.
Return ACCEPT, or REJECT with the specific missing evidence.

"Reach says 4,000 users: show the query" is the whole cultural point. Prioritization debates die in meetings because numbers are unfalsifiable. Here every number is one click from its provenance, and the judge enforced that before any human saw it.

The override file. You will sometimes rank below-the-line work first: a strategic bet, a contractual commitment, a founder conviction. overrides.json records each as data: problem ID, direction, reason, author, date. The table renders the evidenced rank and the overridden rank side by side. Scores stay untouched by politics, and the politics are at least written down.


Phase 4: The weekly refresh

Run /rice-refresh interactively until a full run produces a table you'd defend in a roadmap meeting. Then schedule it: /schedule creates a cloud routine ("Mondays 6am: run /rice-refresh") that clones the repo's default branch, loads .claude/ and .mcp.json, runs autonomously, and pushes commits on a claude/ branch.

Continuity across runs is the committed state, read at run start: every scheduled session inherits problems.json, overrides.json, and last week's scores by cloning the repo, which is why step 1 of the skill reads them first. Practicalities: configure MCP credentials in the routine's environment variables, decide whether a human merges the weekly branch or you allow direct push, and add a Stop hook posting run status to Slack so a failed Monday run doesn't surface on Thursday. Self-hosted works too: cron plus headless claude -p "/rice-refresh", or a GitHub Actions cron with the official claude-code-action.


Phase 5: Sharing with the team

  • The table as a stable URL. Publish ranked-table.md as an artifact on claude.ai; republishing reuses the URL, so "the prioritization table" is one bookmark. On Team and Enterprise plans you can scope visibility to your organization. One caveat to design around: artifact publishing works from the CLI and desktop app, not from every automated surface, so in a fully autonomous setup the publish step may be the one human touch, or you swap in your own hosting.
  • Slack carries deltas only. The weekly post is rank changes, new candidate problems awaiting confirmation, and anything the judge left UNSCORED. If nothing moved, it says so in one line. A feed of changes gets read; a weekly repost of a 40-row table gets muted.

Phase 6: Maintenance (what you own now)

  • Source drift. Sales switches note-taking tools, support changes ticket fields, an extractor silently starts returning nothing. Add per-source volume checks to the skill: a channel at zero for two weeks is an alert, not a quiet decline in Confidence scores.
  • Registry hygiene. Problems merge (two IDs were one problem) and split (one ID was two). Each is a mapping entry plus a human decision, and skipping the mapping quietly breaks trend lines. Budget a short weekly triage of candidates and merge proposals, assigned to a person by name.
  • Score calibration reviews. Quarterly, compare shipped initiatives' predicted Reach and Impact against what actually happened. This is the eval loop for the scoring prompts; without it, the Impact scale drifts into decoration.
  • Eng estimate staleness. The judge checks that estimates exist and are current, but someone has to make refreshing them part of eng's routine, or Effort becomes the least trustworthy column.
  • Prompt drift. Scorer and judge prompts are code. PR review on .claude/ changes, because a prompt edit changes your roadmap.

What this costs

This is one of the cheaper workflows in this series to run, and the assumptions are worth stating:

  • The weekly refresh fits subscription usage. 40 scorer fan-outs each reading a small problem record, running 4 targeted queries, and returning compact JSON, plus one judge pass, is modest token volume. The queries themselves are MCP calls and script runs, costing nothing beyond the tokens describing them. Routine runs draw on plan usage; watch /usage trends.
  • API spend appears only in intake extraction, and only at volume. At 200-400 short requests a week, extraction through a cheap model is small change. If you're feeding full sales call transcripts rather than notes, you've re-created the conversation intelligence workload, and its cost profile (metered Batch API spend scaling with volume) applies to that pipeline, not this one.
  • The unbilled line. Someone wires four extractors, tunes a dedupe threshold, triages candidates weekly, and runs calibration reviews quarterly. That is the real price of "free," and the number to hold against the renewal quote.

What ProductBoard gives you that this does not (and vice versa)

Being fair to the incumbent: ProductBoard-class tools give you a mature UI your whole org can use without training, a customer-facing portal for collecting requests and closing the loop, integrations someone else maintains, and vendor support when things break. If your bottleneck is getting sales and support to contribute at all, a polished tool they'll actually open beats a better-architected one they won't.

What this build gives you that those tools structurally don't: Reach pulled from your analytics by query, not typed from memory. Confidence computed from evidence volume and diversity instead of a gut slider. Dedupe by meaning rather than by whoever notices two similar cards. A judge that rejects unevidenced scores. Git history for every score, override, and registry change.

The clean statement of the trade: ProductBoard organizes what humans put into it, superbly. This build generates and verifies the numbers itself, and in exchange you own a small data system. If nobody wants to own extractors and a registry, buy the tool. If your prioritization fights are really fights about whose numbers are real, evidence-backed scores are the thing no amount of UI polish buys.


The four practical challenges

  1. Setup at scale: not raw volume this time, but fragmentation. Four intake extractors, embedding dedupe into a stable registry, and a 40-agent scoring fan-out (Phases 1-3), because 160 evidence queries and hundreds of scattered requests can't live in one session's context or one person's head.
  2. Sharing with the team: the stable-URL table plus a changes-only Slack feed (Phase 5), with the usual artifact caveats: CLI and desktop publishing, org visibility on Team and Enterprise plans.
  3. Maintenance: source drift, registry merges and splits, calibration reviews, estimate staleness (Phase 6). A prioritization system nobody maintains converges on the same unfalsifiable spreadsheet you left.
  4. Cost: light on tokens because the evidence work is queries, not reading; the real spend is engineering time, which is exactly the line item the ProductBoard comparison forces you to price.

The managed alternative

The managed side of this comparison is the post's premise: ProductBoard, with Aha! and airfocus in the same aisle, gives you the mature UI, the customer portal, and the out-of-the-box integrations, in exchange for scores that are only as evidence-backed as what humans type into them. The section above lays out that trade in detail. The short version: buy the tool if your gap is organizing and communicating priorities; build the pipeline if your gap is trusting the numbers behind them.

Doing this with Second Axis

Everything above is the do-it-yourself path. On Second Axis, prioritization is a workflow in the marketplace: connect your analytics, issue tracker, and CRM, choose the workflow, and set the goal ("a ranked, evidence-cited prioritization table, refreshed weekly, with rank changes announced"). It runs on schedule with the request dedupe, evidence queries, RICE scoring, and judge checks built in, and it monitors itself: a broken connector, a failed run, or a score that lost its evidence trail gets flagged and alerted instead of shipping into your roadmap review.