Skip to content

Instantly share code, notes, and snippets.

@alexng353
Last active February 16, 2026 23:49
Show Gist options
  • Select an option

  • Save alexng353/fd47bb52f2ad998f08c018795e5ef728 to your computer and use it in GitHub Desktop.

Select an option

Save alexng353/fd47bb52f2ad998f08c018795e5ef728 to your computer and use it in GitHub Desktop.
ez - batch linear issue fixer using claude code

ez

Batch Linear issue fixer using Claude Code. Takes Linear issue URLs, creates git worktrees, spawns Claude Code to implement fixes, and creates PRs — all unattended.

With --review, spawns parallel reviewers after all fixes are done that review each PR, fix issues directly in code, and push.

Prerequisites

Install

curl -fsSL https://gist.githubusercontent.com/alexng353/fd47bb52f2ad998f08c018795e5ef728/raw/062f78f65053aec40e47221846300b395360cc25/ez -o ~/.local/bin/ez
chmod +x ~/.local/bin/ez # make sure wherever you install it is in your $PATH

Or just copy the ez file to somewhere on your $PATH.

Configuration

All config is via environment variables:

Variable Default Description
EZ_REPO_ROOT ~/code/futurity/futurity.git Path to your bare git repo
EZ_MODEL (claude default) Model for the fix task
EZ_REVIEW_MODEL (claude default) Model for the review task
EZ_BRANCH_MODEL sonnet Model for branch name lookup (cheap/fast is fine)

You almost certainly want to set EZ_REPO_ROOT:

export EZ_REPO_ROOT="$HOME/code/myorg/myrepo.git"

Usage

# Single issue
ez https://linear.app/myteam/issue/PF-254/fix-the-thing

# Multiple issues (processed sequentially)
ez https://linear.app/.../PF-254/... https://linear.app/.../PF-255/...

# Newline-separated
ez "https://linear.app/.../PF-254/...
https://linear.app/.../PF-255/..."

# Piped
echo "https://linear.app/.../PF-254/..." | ez

# With review phase (reviewers run in parallel after all fixes)
ez --review https://linear.app/.../PF-254/... https://linear.app/.../PF-255/...

How it works

For each Linear URL:

  1. Extracts the issue ID (e.g. PF-254) from the URL
  2. Calls Claude Code to fetch the gitBranchName from Linear via MCP
    • I am aware that you can do this via the CLI. I don't care. Fork this script if you want to.
  3. Creates a git worktree for that branch (or reuses an existing one)
  4. Runs bun install in the worktree
  5. Spawns Claude Code with --dangerously-skip-permissions to:
    • Read the full issue from Linear
    • Explore the codebase and implement the fix
    • Lint, type-check, commit, push, and create a PR

If --review is passed, after all fixes complete, it spawns one reviewer per PR in parallel. Each reviewer reads the issue + diff, fixes any problems it finds directly in code, and pushes.

Notes

  • Uses --dangerously-skip-permissions so Claude can run without interactive approval prompts. Review the Claude Code docs to understand the implications.
  • Assumes a bare-repo + worktree workflow. Each branch gets its own worktree directory under $EZ_REPO_ROOT.
  • The script blocks plan mode (--disallowedTools EnterPlanMode) so Claude goes straight to implementation.
  • Review logs are written to a temp directory (printed at the end).
#!/usr/bin/env bash
set -euo pipefail
# ez — Batch Linear issue fixer using Claude Code
#
# Takes Linear issue URLs, and for each one (sequentially):
# 1. Fetches the git branch name from Linear
# 2. Creates a worktree (using the same logic as `work`)
# 3. Spawns Claude Code to implement the fix
# 4. Claude commits and creates a PR
#
# With --review, after all workers finish, spawns N reviewers in parallel
# that review each PR, fix any issues found, and push.
#
# Usage:
# ez [--review] <linear-url> [<linear-url> ...]
# ez --review "url1
# url2
# url3"
# echo "url1\nurl2" | ez --review
#
# Config (env vars):
# EZ_REPO_ROOT — bare repo root (default: ~/code/futurity/futurity.git)
# EZ_MODEL — model for the fix task (default: unset, uses claude default)
# EZ_REVIEW_MODEL — model for the review task (default: unset, uses claude default)
# EZ_BRANCH_MODEL — model for branch name lookup (default: sonnet)
# Render basic markdown formatting for terminal output
render_md() {
perl -pe '
s/\*\*`([^`]+)`\*\*/\e[1;34m$1\e[0m/g;
s/\*\*([^*]+)\*\*/\e[1m$1\e[0m/g;
s/`([^`]+)`/\e[34m$1\e[0m/g;
s/(?<!\*)\*([^*\n]+)\*(?!\*)/\e[3m$1\e[0m/g;
'
}
REPO_ROOT="${EZ_REPO_ROOT:-$HOME/code/futurity/futurity.git}"
FIX_MODEL="${EZ_MODEL:-}"
REVIEW_MODEL="${EZ_REVIEW_MODEL:-}"
BRANCH_MODEL="${EZ_BRANCH_MODEL:-sonnet}"
# Parse flags and collect URLs
do_review=false
raw_args=()
for arg in "$@"; do
if [[ "$arg" == "--review" ]]; then
do_review=true
else
raw_args+=("$arg")
fi
done
# Collect URLs from remaining args (re-split on newlines)
urls=()
for arg in "${raw_args[@]+${raw_args[@]}}"; do
while IFS= read -r line; do
trimmed=$(echo "$line" | xargs)
[[ -n "$trimmed" ]] && urls+=("$trimmed")
done <<< "$arg"
done
# Read from stdin if it's not a terminal (piped input)
if [[ ! -t 0 ]]; then
while IFS= read -r line; do
trimmed=$(echo "$line" | xargs)
[[ -n "$trimmed" ]] && urls+=("$trimmed")
done
fi
if [[ ${#urls[@]} -eq 0 ]]; then
echo "Usage: ez [--review] <linear-url> [<linear-url> ...]"
echo ""
echo "Process Linear issues sequentially: for each issue, create a worktree,"
echo "spawn Claude Code to implement the fix, and create a PR."
echo ""
echo "Options:"
echo " --review After all fixes, spawn parallel reviewers that review each PR,"
echo " fix any issues found, and push."
echo ""
echo "URLs can be space-separated args, newline-separated in quotes, or piped via stdin."
exit 1
fi
cd "$REPO_ROOT"
git fetch --quiet
total=${#urls[@]}
current=0
failed=()
# Track completed issues for the review phase: "worktree_path|issue_id|url"
completed=()
for url in "${urls[@]}"; do
current=$((current + 1))
# Extract issue ID from URL (e.g., PF-254 from .../issue/PF-254/slug)
if [[ "$url" =~ issue/([A-Z]+-[0-9]+) ]]; then
issue_id="${BASH_REMATCH[1]}"
else
echo "WARNING: Could not extract issue ID from: $url — skipping"
failed+=("$url (bad URL)")
continue
fi
echo ""
echo "================================================================"
echo " [$current/$total] $issue_id"
echo " $url"
echo "================================================================"
# Get branch name from Linear via Claude Code
echo "-> Fetching branch name from Linear..."
branch_name=$(claude -p --model "$BRANCH_MODEL" \
"Use the mcp__linear-server__get_issue tool to get issue '$issue_id'. Return ONLY the gitBranchName value. No markdown, no explanation, no quotes — just the raw branch name." \
2>/dev/null | tail -1 | xargs) || true
if [[ -z "$branch_name" ]]; then
echo "WARNING: Could not get branch name for $issue_id — skipping"
failed+=("$issue_id (no branch name)")
continue
fi
echo "-> Branch: $branch_name"
# Create worktree (mirrors the `work` function)
if [[ -d "$branch_name" ]]; then
echo "-> Worktree already exists"
elif git show-ref --verify --quiet "refs/heads/$branch_name" 2>/dev/null; then
echo "-> Local branch exists — creating worktree"
git worktree add "$branch_name" "$branch_name"
(cd "$branch_name" && bun i)
elif git show-ref --verify --quiet "refs/remotes/origin/$branch_name" 2>/dev/null; then
echo "-> Remote branch exists — creating worktree tracking origin"
git worktree add --track -b "$branch_name" "$branch_name" "origin/$branch_name"
(cd "$branch_name" && bun i)
else
echo "-> New branch — creating worktree"
git worktree add -b "$branch_name" "$branch_name"
(cd "$branch_name" && bun i)
fi
worktree_path="$REPO_ROOT/$branch_name"
# Build the claude command
# --dangerously-skip-permissions: no tool approval prompts
# --disallowedTools: block plan mode so claude just executes directly
claude_args=(-p --dangerously-skip-permissions --disallowedTools EnterPlanMode)
if [[ -n "$FIX_MODEL" ]]; then
claude_args+=(--model "$FIX_MODEL")
fi
# Run Claude Code in the worktree to fix the issue and create a PR
# Pipe prompt via stdin to avoid multi-line arg issues
echo "-> Spawning Claude Code..."
if (cd "$worktree_path" && cat <<PROMPT | claude "${claude_args[@]}" | render_md
You are fixing Linear issue $issue_id.
Linear URL: $url
Instructions:
1. Use the mcp__linear-server__get_issue tool to read the full issue details for '$issue_id'
2. Explore the codebase to understand the relevant code and architecture
3. Implement the fix
4. Run 'bun run lint' to lint and format your changes
5. Run 'bun run check' to type-check all packages
6. Fix any lint or type errors in files you changed (repeat steps 4-5 until clean)
7. Stage and commit all changes. Prefix the commit message with '[agent]' and end with:
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8. Push the branch and create a PR with 'gh pr create':
- Prefix the PR title with '[agent]'
- Include a summary of what was changed and why
- Include the Linear issue link: $url
- End the PR body with: Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Do NOT use plan mode. Go straight to implementation.
Follow all guidelines in CLAUDE.md. Do not over-engineer — make the minimum changes necessary.
PROMPT
); then
echo ""
echo "OK: $issue_id"
completed+=("${worktree_path}|${issue_id}|${url}")
else
echo ""
echo "WARNING: Claude Code exited with an error for $issue_id"
failed+=("$issue_id (claude error)")
fi
done
echo ""
echo "================================================================"
echo " Fixes done — $total issue(s), ${#completed[@]} succeeded, ${#failed[@]} failed"
if [[ ${#failed[@]} -gt 0 ]]; then
for f in "${failed[@]}"; do
echo " FAIL: $f"
done
fi
echo "================================================================"
# ── Review phase ──────────────────────────────────────────────────────────────
if $do_review && [[ ${#completed[@]} -gt 0 ]]; then
echo ""
echo "================================================================"
echo " Spawning ${#completed[@]} reviewer(s) in parallel..."
echo "================================================================"
review_claude_args=(-p --dangerously-skip-permissions --disallowedTools EnterPlanMode)
if [[ -n "$REVIEW_MODEL" ]]; then
review_claude_args+=(--model "$REVIEW_MODEL")
fi
pids=()
review_issues=()
log_dir=$(mktemp -d)
for entry in "${completed[@]}"; do
IFS='|' read -r wt_path r_issue_id r_url <<< "$entry"
review_issues+=("$r_issue_id")
log_file="$log_dir/$r_issue_id.log"
echo "-> Reviewer for $r_issue_id (log: $log_file)"
(
cd "$wt_path"
# Find the PR number for this branch
pr_url=$(gh pr list --head "$(git rev-parse --abbrev-ref HEAD)" --json url --jq '.[0].url' 2>/dev/null || echo "")
cat <<REVIEW | claude "${review_claude_args[@]}"
You are reviewing and fixing a PR for Linear issue $r_issue_id.
Linear URL: $r_url
PR: $pr_url
Instructions:
1. Use the mcp__linear-server__get_issue tool to read the full issue details for '$r_issue_id'
2. Run 'gh pr diff' or 'git diff main...HEAD' to review all changes in this branch
3. Review the code thoroughly:
- Does it actually fix the issue described in the Linear ticket?
- Are there bugs, logic errors, or security issues?
- Does it follow the project's code style and patterns?
- Are there edge cases not handled?
4. If you find issues: fix them directly in the code. Do NOT post review comments.
Do NOT ask for permission. Just fix the problems.
5. After fixing, run 'bun run lint' and 'bun run check'
6. If you made changes, commit them with a message like:
[agent] review fixes for $r_issue_id
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7. Push the fixes: 'git push'
8. If no issues found, just confirm the PR looks good — no need to commit anything.
Do NOT use plan mode. Go straight to review.
Do NOT post review comments to GitHub. Fix issues directly in the code.
Follow all guidelines in CLAUDE.md.
REVIEW
) > "$log_file" 2>&1 &
pids+=($!)
done
echo ""
echo "-> Waiting for all reviewers to finish..."
review_failed=()
for i in "${!pids[@]}"; do
if wait "${pids[$i]}"; then
echo " OK: ${review_issues[$i]}"
else
echo " FAIL: ${review_issues[$i]}"
review_failed+=("${review_issues[$i]}")
fi
done
echo ""
echo "================================================================"
echo " Reviews done — ${#completed[@]} reviewed, ${#review_failed[@]} failed"
if [[ ${#review_failed[@]} -gt 0 ]]; then
for f in "${review_failed[@]}"; do
echo " FAIL: $f"
done
fi
echo " Logs: $log_dir"
echo "================================================================"
fi
echo ""
echo "All done."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment