Engineering Workflow·

How We Keep Mobile App Bugs From Becoming Tech Debt

A look at the issue-pipeline workflow my team uses to triage bugs, split parallel work safely, and keep our private mobile app repo from accumulating avoidable tech debt.
Developer debugging code while organizing issues into a repair pipeline

The Problem With Bug Queues

My team and I are building a mobile app in a private repo. Like any real app, bugs do not arrive politely one at a time. They come in clusters: a UI edge case here, a state bug there, a data mismatch somewhere else, and a vague "this flow feels broken" report that might be a real product problem or might just need better reproduction steps.

If we treat every issue as a separate emergency, two bad things happen.

First, we lose time to context switching. Every developer or AI coding session has to re-read the repo, understand the app architecture, figure out which files matter, and rediscover which issues overlap.

Second, the codebase starts collecting cleanup debt. A rushed bug fix might solve the visible symptom but leave duplicated logic, unclear ownership, missing tests, or a hidden second bug in the same path.

That is what the issue-pipeline repo is built to prevent.

It is not a magic bug-fixing bot. It is a small set of Claude Code slash commands plus a deterministic shell library that turns a pile of GitHub issues into scoped, reviewable, parallel work streams.

The main idea is simple: diagnose before changing code.

The Pipeline Shape

The workflow has three main commands:

    

There is also a parallel version of the first step:

    

The flow looks like this:

  1. Read and diagnose a batch of issues.
  2. Group issues into streams based on code ownership and file overlap.
  3. Create one git worktree per stream.
  4. Run each stream in its own Claude Code session.
  5. Commit per task and open separate PRs.
  6. Let GitHub close the issues when the PRs merge.

Each stream gets its own branch, worktree, terminal, prompt, and PR. That isolation is the part that keeps the work from turning into one giant risky branch.

Phase 0: Investigation First

/issue-phase-0 is read-only. No code edits. No worktrees. No "while I am here" cleanup.

It starts with a precheck through the shell library:

    

That verifies the session is running from the main checkout, on the default branch, with a clean working tree. That sounds basic, but it matters. A dirty tree at the start of a bug batch is how unrelated local changes accidentally get folded into bug-fix PRs.

Then the pipeline asks which issues to work on. We can give it specific issue numbers, a gh issue list filter, or something like "all open bugs." It fetches the issues, filters out non-actionable items, and asks for confirmation before proceeding.

The useful part is the diagnosis report. Phase 0 writes:

    

That report captures:

  • which issues were investigated
  • the likely root cause or implementation approach
  • the files each issue probably touches
  • which issues can be worked in parallel
  • which issues share a root cause
  • which work must be serialized
  • the recommended merge sequence
  • the tests required per stream

This is where tech debt gets stopped early. We are not just asking "how do I patch bug #142?" We are asking "does bug #142 share files, state, or root cause with #138 and #145, and should those land together or separately?"

That is the difference between bug fixing and codebase maintenance.

Phase 0 Parallel: Scaling The Diagnosis

For bigger batches, /issue-phase-0-parallel fans out one investigator subagent per issue.

Each investigator is read-only and has one job: inspect a single issue and write a JSON report with the title, diagnosis, likely files touched, dependencies, prefix, scope, confidence, and notes.

The orchestrator then aggregates those reports and builds the stream graph:

  • issues that touch the same files go into the same stream
  • explicit dependencies become ordering edges
  • low-confidence investigations are flagged
  • suspected duplicates are surfaced for human review
  • failed investigators go into an "Unanalyzed" bucket instead of being silently retried

That last point is important. The pipeline does not pretend that every AI investigation succeeded. If an issue is unclear, malformed, or under-investigated, the report says so. That prevents false confidence from becoming production code.

Creating Worktrees

After reviewing the Phase 0 report, we run:

    

This is setup only. It does not fix anything.

It parses the diagnostic report, pulls the latest default branch, and creates one worktree per stream:

    

Each worktree gets a branch with a conventional prefix:

    

The shell library validates those prefixes. That small constraint gives us predictable branches and commit history. Bugs become fix/..., cleanup work becomes refactor/..., test-only work becomes test/..., and so on.

The setup command also copies local environment files into the worktree, detects the package manager from the lockfile, installs dependencies, creates a .claude directory, and writes a stream-specific prompt:

    

That prompt contains the stream's issues, branch, file scope, ordered tasks, constraints, and stop condition.

The result is not "Claude, go fix some bugs." It is "Claude, in this worktree, on this branch, for these issues, touch this scope, run these checks, commit each task, open a PR, and stop."

That level of specificity is what keeps AI-assisted development from turning into random walk refactoring.

Running A Stream

Inside each worktree, we run:

    

The command verifies that it is actually running inside a recognized stream worktree. If someone accidentally runs it from the main checkout, it stops.

Then the stream session reads .claude/STREAM_PROMPT.md and follows the workflow:

  1. Read the Phase 0 stream section.
  2. Read each linked issue with gh issue view.
  3. Confirm the file-change scope.
  4. Implement the tasks in order.
  5. Commit per task with issue references.
  6. Push the branch.
  7. Open a PR with Closes #N.
  8. Stop after the PR is opened.

That stop condition matters. The stream does not self-merge. It does not manually close issues. It does not keep wandering after the PR is open.

Human review stays in the loop.

Why This Keeps Tech Debt Low

The pipeline helps because it turns implicit engineering discipline into explicit steps.

Clean starting point. The precheck rejects dirty working trees and non-default branches before investigation begins.

No code before diagnosis. Phase 0 produces a report before anyone edits files. That gives us a shared map of root causes, file overlap, and merge risk.

Parallel work is based on file scope. Streams are grouped by actual code paths, not by gut feel. If two issues touch the same files, they land in the same stream or get serialized.

Every stream has boundaries. The stream prompt names the files in scope and tells the agent not to edit outside them.

Small commits are expected. The stream workflow asks for one commit per task or cohesive group, each referencing the relevant issue.

PRs close issues, not agents. The PR body uses Closes #N, so GitHub closes issues only when the reviewed code lands.

Low-confidence work is surfaced. The parallel phase has explicit buckets for unanalyzed issues, suspected duplicates, and low-confidence investigations.

Deterministic operations are not left to the LLM. The shell library owns project discovery, status moves, worktree creation, package manager detection, and stream prompt writes.

That last point is the design center. The LLM should diagnose, group, and explain. The script should do the repeatable plumbing.

The Shell Library Is The Guardrail

issue-pipeline-lib.sh is the boring part, which is exactly why it is valuable.

It provides subcommands for:

  • precheck
  • detect-pm
  • discover-project
  • config-load
  • move-issue
  • create-worktree
  • write-stream-prompt
  • in-worktree-check

Those commands handle the details that should not depend on prompt quality: checking git state, finding the GitHub Project, moving issues to "Investigating" or "In Progress", creating branches and worktrees, and verifying the current session is in the right place.

The repo also has a two-tier test harness:

    

Tier 1 covers pure bash logic like package-manager detection, worktree checks, branch-prefix validation, and prompt writes. Tier 2 stubs GitHub CLI paths for project discovery and issue status moves. In the local copy I inspected, Tier 1 has 38 assertions and Tier 2 has 20.

For a workflow that is supposed to keep bugs controlled, testing the workflow itself is not optional.

The Parts We Still Treat Carefully

This pipeline is useful, but it is not a replacement for judgment.

The .env copy step is a plain copy. That is convenient for local development, but it means we need to be careful about what lives in those files and where worktrees are created.

The cached GitHub Project config is scoped to one repo. That is fine for our private mobile app repo, but it is not a multi-repo portfolio scheduler.

The tests cover the shell library, not live GitHub end-to-end behavior. That is the right tradeoff for fast local confidence, but it means we still pay attention during first use in a real repo.

And most importantly, the pipeline stops at PR creation. Review is still review. If the implementation looks too broad, misses tests, or drifts from the stream prompt, we fix that before merge.

The Bigger Lesson

The most valuable thing about issue-pipeline is not that it lets us run several AI coding sessions at once. Parallelism is nice, but uncontrolled parallelism just creates merge conflicts faster.

The valuable thing is that it forces structure:

  • diagnose first
  • group by code ownership
  • isolate work
  • commit in small units
  • verify each stream
  • keep humans in the merge path

That is how we keep our mobile app moving without letting every bug fix become a little pile of hidden debt.

AI can write a lot of code quickly. The hard part is making sure the code lands in the right shape. For us, the issue pipeline is the layer that turns a messy issue queue into bounded engineering work.