Building a Linear-Driven Agent Loop with Claude Code

9 min read

In December, the developer community on X was buzzing about Ralph Wiggum. If you missed it, Anthropic’s Claude Code plugins had a plugin called Ralph Wiggum. In the README it’s described as:

Ralph is a development methodology based on continuous AI agent loops. As Geoffrey Huntley describes it: “Ralph is a Bash loop” - a simple while true that repeatedly feeds an AI agent a prompt file, allowing it to iteratively improve its work until completion.

This was used in a variety of ways. Two common ones were:

  1. Unleash an agent to work on a single task on its own until it was done.
  2. Unleash an agent to iterate through a backlog of work until it had completed all of it.

Today we’re going to explore the second one, using an agent loop to iterate through a project backlog.

Where Ralph Wiggum Falls Flat

The Ralph Wiggum plugin provides a command you call inside Claude Code. The session continues until a set of requirements have been met, at which point the loop exits. For example:

/ralph-loop "Build a REST API for todos. Requirements: CRUD operations, input validation, bin/rails test and bin/rails lint must pass. Output <promise>COMPLETE</promise> when done."

There is a drawback to this approach though. Running the loop inside of a Claude Code session means we’re eating away at our context window. If you’ve read my blog post on Understanding Claude Code’s Context Window then you know that this can cause poor results as time goes on. This becomes exponentially worse if you are trying to loop through multiple pieces of work. The agent’s context window will be subject to context rot as different streams of work are worked on.

There is a solution though.

Bash Loops

Instead of running a Ralph Wiggum loop inside of the Claude Code instance, we can loop inside bash. In this version every iteration of the loop starts with a fresh context window, avoiding issues with context rot. This works via the --dangerously-skip-permissions flag, which allows Claude Code to run non-interactively without prompting for tool approvals. An example loop looks something like:

while true; do
  SESSION=$((SESSION + 1))
  TIMESTAMP=$(date +%Y%m%d_%H%M%S)
  COMMIT=$(git rev-parse --short=6 HEAD 2>/dev/null || echo "no-git")
  LOGFILE="${LOG_DIR}/${AGENT_NAME}_${TIMESTAMP}_${COMMIT}.log"

  echo "--- Session #${SESSION} starting at $(date) ---"
  echo "    Log: $LOGFILE"

  claude --dangerously-skip-permissions \
    -p "$(cat "$PROMPT_FILE")" \
    --model "$MODEL" \
    &>"$LOGFILE" || true

  echo "    Session #${SESSION} ended at $(date)"
  echo ""

  # Brief pause between sessions to avoid hammering if something is broken
  sleep 5
done

The $PROMPT_FILE is where the real work gets defined. It’s a markdown file that tells the agent exactly what to do during each session. Mine walks the agent through a full lifecycle: orient itself on the project, pick up the next issue from Linear, build the feature, run a code review with subagents, and open a pull request. It also includes guardrails like one issue per session, never break main, and what to do if blocked or stuck for more than 15 minutes.

Let’s walk through how each of these pieces works in practice.

How It All Fits Together

I decided to give this a try on my recent project CreatorSignal that I’ve been building during my live streams. While I’ve seen many people maintaining their backlogs in markdown files or custom Kanban board experiences within Claude Code, I prefer using Linear. I didn’t want to recreate a task management system just for the agent loop. With the Linear MCP in hand, here’s how I set it up.

PROGRESS.md

One of the core pieces is the PROGRESS.md file. While the individual tasks are tracked and maintained in Linear, this file is meant to serve as a sort of “memory” for the agents to understand what has been accomplished from a more holistic level. At the start of each loop, the PROGRESS.md file is read in. At the end of a loop, the agent writes to it what it has accomplished.

Example:

# Progress

## 2026-02-13

### PRX-27: Billing portal (Stripe Customer Portal integration) — DONE
- Created `BillingPortalController` with `show` and `create` actions
- Billing page displays current plan, price, next billing date
- "Manage Subscription" button creates Stripe BillingPortal::Session and redirects
- Free users see upgrade CTA; former subscribers can still access portal for invoices
- Cancellation pending state shown with reactivation option
- 11 request specs + 6 system specs, all passing (266 total)
- PR: https://github.com/dgalarza/CreatorSignal/pull/31
- Branch based on PRX-25 (chain: PRX-17 → PRX-23 → PRX-24 → PRX-25 → PRX-27)

Implementing an Issue

Using the Linear MCP, the agent finds the next highest priority issue to work on. It starts by looking at the “Todo” column and picks the next one up. If there’s nothing in Todo, it checks the backlog instead. From there it reads the issue’s details to understand the work that needs to be done. For the loop to work well, issues need to be spec’d out thoroughly. This gives the agent the highest chance of performing quality work without human supervision.

With an issue selected, the agent moves it to “In Progress”, creates a branch, and starts building. A task is not considered “done” unless the test suite and linters both pass. This is another critical piece for a successful agent loop. The agent must have solid ways of verifying its own work. Without automated checks, it’s difficult for the agent to understand success, and quality drops.

When the agent believes its work is ready, it comments on the Linear issue with a summary of what it built and moves the issue to “In Review”.

Code Review

Similar to my workflow described in How I Use Claude Code, the next step is to spawn subagents to perform code review. The agent uses the Task tool to spin up a reviewer that evaluates the diff against the issue requirements, checking for correctness, test quality, Rails conventions, security, and performance.

The review is posted as a comment on the Linear issue. This provides visibility into the full lifecycle of the work. I can see the main agent’s implementation summary alongside the code review feedback. The agent then resolves any feedback it received and posts a final comment on the Linear issue summarizing its decisions.

Pull Request

After the code review process is complete and feedback is addressed, the agent commits the work and opens a pull request. The Linear issue is moved to “Done”, and the agent writes its progress update to the PROGRESS.md file.

Clean Up

With everything complete, the agent’s last instructions are to check out the main branch and rebase against origin/main so that the next loop starts in a fresh state. The loop then exits cleanly. There’s a built-in pause after each iteration before the next one starts.

Visibility

This loop proved to work well. I connected Slack to my Linear project so I could see notifications coming in as the agent worked through issues. Each time an issue had its status updated, each time an agent completed its work, and each time an agent received and addressed review feedback, I could see the progress in real time.

Improving on the Workflow

While this initial pass at a loop was working well, I had some things I wanted to improve. First, as pull requests were getting opened and merged, some would end up becoming stale with merge conflicts given the speed at which new features were landing. Second, I wanted to be able to leave feedback on a pull request as if I was working with a team member and have it get addressed by the agent as part of the loop.

I solved this by adding a new step to the loop as follows.

Before picking up a new task, the agent runs bin/pr_check. This script looks through my open pull requests for any with the “needs-revision” label. If none need review feedback addressed, it checks for any that have gone stale with merge conflicts.

If a PR like this is found, the loop addresses one PR leaving the next for the next loop iteration. So whenever I had a PR that I felt had feedback I wanted addressed, I would leave comments on it and add the “needs-revision” label. The next time a loop happens the agent will pick it up and address the feedback.

# bin/pr_check
#
# Finds the first open PR that needs attention.
# Returns JSON with PR details if one needs work, or empty output if all clean.
#
# A PR "needs attention" if:
#   1. It has merge conflicts (mergeableStatus == CONFLICTING)
#   2. It has the "needs-revision" label
#
# Usage:
#   bin/pr_check           # returns JSON or empty
#   bin/pr_check --quiet   # exit code only (0 = needs attention, 1 = all clean)
#
# Output format:
#   {
#     "number": 42,
#     "branch": "damian/prx-7-exa-research-tools",
#     "title": "PRX-7: Exa research tools",
#     "url": "https://github.com/...",
#     "reason": "has_feedback",    # or "conflicting" or "conflicting,has_feedback"
#     "conflicting": true,
#     "has_feedback": true
#   }

The loop itself is about a 100 line bash script. I’ll be adding it to my Claude Code workflows this week and sharing it with my newsletter.

What Makes This Work

After running this loop across several sessions, a few things stand out as critical to getting quality results:

  1. Fresh context per iteration. Running the loop in bash instead of inside a Claude Code session means each task gets a clean context window. This is the single biggest difference from the Ralph Wiggum approach.
  2. Well-spec’d issues. The agent is only as good as the instructions it receives. Vague issues produce vague results. Detailed acceptance criteria and clear scope make all the difference.
  3. Automated verification. Requiring passing tests and linters before a task is considered “done” gives the agent a concrete definition of success. Without this, quality drops fast.
  4. Linear as the source of truth. Using an existing project management tool instead of reinventing one means I can see the full lifecycle of every issue, from backlog to done, with comments and status updates along the way.

The combination of these pieces turns what could be a chaotic autonomous loop into something that produces reviewable, mergeable work. It’s not perfect, and I still review every pull request before merging, but the amount of ground it covers between review cycles is significant.

Additional Reading

If you haven’t already, sign up for my newsletter for weekly emails on AI Engineering and agentic development workflows.

More on building real systems

I write about AI integration, architecture decisions, and what actually works in production.

Occasional emails, no fluff.

Powered by Buttondown