You know that feeling when you’re waiting for a massive codebase refactor to finish, or you’ve kicked off a 20-minute test suite run, and you just… can’t move forward until it completes? Yeah. That’s where background tasks come in.
Claude Code lets you fire off long-running operations without tying up your interactive session. Start a major refactor, generate documentation across 50 files, run a comprehensive test suite—then step away. Check back whenever you want. No babysitting required.
Let’s talk about what background tasks actually do, how to use them effectively, and when they’ll save your sanity.
What Are Background Tasks, Anyway?
A background task is a long-running operation that executes on Claude’s infrastructure without blocking your interactive session. You trigger it, it goes to work, and you get back to other things.
Think of it like this: normally, when you ask Claude Code to do something, you’re in a conversation. You ask, Claude responds, you read, you ask again. It’s interactive. But if you ask for something that takes 20 minutes—like refactoring all your authentication across 15 files—you’d be stuck waiting.
Background tasks solve that. You say “start a background task,” it runs independently, and you can either keep working on other things in Claude or literally close your laptop.
Why Background Tasks Matter
There are specific moments when background execution isn’t just nice—it’s essential.
Large refactors are the obvious one. If you’re renaming a core function across your entire codebase, or migrating from one library to another, that’s not a quick 30-second operation. That’s systematic, methodical work across dozens of files.
Test suite runs are another big one. You’ve changed something fundamental. You want to know if the whole test suite passes. That run might take 15 minutes. You don’t want to sit there watching output scroll by.
Documentation generation eats time too. You’ve got 40 React components, and you want comprehensive documentation for each one. That’s data extraction, analysis, writing, formatting—for every single component. An hour of work that you don’t want to manually orchestrate.
Code analysis and validation can run long, especially if you’re scanning a large codebase for anti-patterns, security issues, or performance problems. You want thorough analysis, not a quick glance.
The pattern here? Whenever you’re asking Claude to do systematic, deep work across many files, background tasks are your friend.
How to Start a Background Task
Starting a background task is straightforward. You use Claude’s background task API:
client = anthropic.Anthropic()
# Start a background task for a large refactor
task = client.beta.background_tasks.create(
request_id="refactor-auth-2026-03-16",
task_type="code_refactor",
description="Migrate authentication from custom JWT to Auth0 across all backend services",
codebase_path="/projects/my-app",
target_files=[
"src/auth/*.ts",
"src/middleware/*.ts",
"src/services/user-service.ts",
"tests/auth/*.test.ts"
],
instructions="Replace all custom JWT handling with Auth0 SDK calls. Update error handling. Preserve existing test structure.",
priority="normal",
timeout_seconds=3600
)
print(f"Task started: {task.task_id}")
print(f"Status: {task.status}")
The key things happening here:
request_id– A unique identifier you create. Helps you track it later.task_type– What kind of task (code_refactor, test_run, doc_generation, etc.)description– What are we actually doing? Be clear.codebase_path– Where the code lives.target_files– Which files to touch. You can use wildcards.instructions– Exactly what you want Claude to do. This is critical—be specific.priority– How urgent it is (normal, high, low).timeout_seconds– How long before we give up (3600 = 1 hour is typical for big jobs).
Once you hit submit, the task is queued. You get back a task ID. And then? You’re done. The operation runs independently.
Checking Task Status
You don’t have to check constantly, but sometimes you’ll want to know: is this done yet? How far along is it?
# Check the status of your background task
task_status = client.beta.background_tasks.retrieve(
task_id="refactor-auth-2026-03-16"
)
print(f"Status: {task_status.status}")
print(f"Progress: {task_status.progress_percent}%")
print(f"Estimated time remaining: {task_status.eta_seconds} seconds")
print(f"Files processed: {task_status.metadata.get('files_processed', 0)}")
print(f"Issues found: {task_status.metadata.get('issues_found', 0)}")
The status field can be:
- queued – Waiting for an available worker
- running – Currently executing
- completed – Finished successfully
- failed – Something went wrong
- cancelled – You stopped it
The progress metadata lets you peek under the hood. How many files has Claude processed? How many issues did it find? This helps you estimate how much longer you’ll wait.
Retrieving Results
When a background task completes, the results are stored and ready to retrieve:
# Get the complete results once finished
results = client.beta.background_tasks.retrieve(
task_id="refactor-auth-2026-03-16"
)
if results.status == "completed":
print(f"Task completed successfully!")
print(f"Output:\n{results.output}")
print(f"Summary:\n{results.summary}")
# Access detailed metadata
changes = results.metadata.get("changes", [])
print(f"\nFiles changed: {len(changes)}")
for change in changes:
print(f" - {change['file']}: {change['description']}")
# Get the generated artifact (like the refactored code)
artifacts = results.artifacts
for artifact in artifacts:
print(f"Generated: {artifact.name}")
The results include:
- output – The main result (refactored code, test output, etc.)
- summary – Human-readable overview of what happened
- metadata – Detailed info (files changed, lines modified, errors, etc.)
- artifacts – Generated files you can download or review
Interactive Sessions vs. Background Tasks
Here’s the key distinction, and it matters:
Interactive sessions are what you’re probably used to. You ask Claude a question, Claude responds, you iterate. Great for discussion, debugging, paired programming. But if the task takes 30+ minutes, you’re stuck waiting.
Background tasks are fire-and-forget. You describe what you want, submit it, and Claude works independently. You can’t interrupt mid-task or ask follow-up questions. But you get back to doing other things now.
When should you use each?
| Task | Mode | Why |
|---|---|---|
| Refactor 20+ files | Background | Long-running, don’t need interaction |
| Debug a specific issue | Interactive | Need back-and-forth, hypotheses |
| Generate docs for 50 components | Background | Systematic, doesn’t need discussion |
| Design a new feature | Interactive | Needs exploration, ideation |
| Run full test suite | Background | Just need results, not real-time output |
| Write a single complex function | Interactive | Might need refinement, discussion |
The rule of thumb: If it’s systematic work across many files and you don’t need to iterate, use background tasks. If it’s exploratory or needs refinement, use interactive sessions.
A Real Workflow Example
Let’s walk through an actual scenario. You’re migrating a codebase from JavaScript to TypeScript. That’s big. Dozens of files. Hundreds of type definitions to create.
Step 1: You start a background task:
migration_task = client.beta.background_tasks.create(
request_id="js-to-ts-migration",
task_type="code_conversion",
description="Convert entire src/ directory from JavaScript to TypeScript",
codebase_path="/projects/my-app",
target_files=["src/**/*.js"],
instructions="""
Convert all JavaScript files to TypeScript:
1. Add .ts extension
2. Generate tsconfig.json if needed
3. Add type annotations for all functions
4. Migrate CommonJS imports to ES6 where appropriate
5. Add JSDoc comments for complex functions
6. Create types/ directory for shared type definitions
Preserve all existing functionality. Don't modify tests yet.
""",
priority="high",
timeout_seconds=7200 # 2 hours
)
print(f"Migration task {migration_task.task_id} started")
Step 2: It runs. You go do other things. Maybe you work on documentation. Maybe you review pull requests. Whatever.
Step 3: An hour later, you check on it:
status = client.beta.background_tasks.retrieve(
task_id="js-to-ts-migration"
)
print(f"Status: {status.status}") # "running"
print(f"Progress: {status.progress_percent}%") # "45%"
It’s halfway done. Good. You come back in another hour.
Step 4: It’s complete. You retrieve the results and integrate them.
Combining Background Tasks with Notifications
Here’s a pro move: combine background tasks with a notification system. You start the task, then get pinged when it’s done:
def wait_for_task_and_notify(task_id, check_interval=30):
"""Check task status periodically and notify when done"""
while True:
status = client.beta.background_tasks.retrieve(task_id=task_id)
if status.status in ["completed", "failed", "cancelled"]:
# Send notification (email, Slack, etc.)
send_notification(
f"Task {task_id} is done!",
f"Status: {status.status}\nSummary: {status.summary}"
)
break
print(f"Task {task_id}: {status.status} ({status.progress_percent}%)")
time.sleep(check_interval)
# Start the notification watcher in a background thread
watcher = threading.Thread(
target=wait_for_task_and_notify,
args=("refactor-auth-2026-03-16",),
daemon=True
)
watcher.start()
# Your code continues...
print("Task started. You'll be notified when it's done.")
Now you can literally forget about the task. You’ll get a Slack message or email when it’s complete.
Fire-and-Forget Patterns
Once you understand background tasks, you can build efficient workflows around them:
# Pattern 1: Queue multiple tasks
tasks = []
for service in ["user-service", "auth-service", "payment-service"]:
task = client.beta.background_tasks.create(
request_id=f"test-{service}",
task_type="test_run",
description=f"Run full test suite for {service}",
codebase_path=f"/projects/{service}",
instructions="Run npm test. Report failures.",
priority="normal"
)
tasks.append(task.task_id)
# Let them all run in parallel
print(f"Started {len(tasks)} test suites")
# Check them all later
time.sleep(1800) # Wait 30 minutes
for task_id in tasks:
status = client.beta.background_tasks.retrieve(task_id=task_id)
print(f"{task_id}: {status.status}")
This is powerful. You can start dozens of operations and let them churn while you do something else entirely.
Handling Failures Gracefully
Background tasks can fail. Network issues. Unexpected code structure. Edge cases. You need to handle that:
def execute_with_fallback(task_id):
"""Execute a task and handle failure"""
max_retries = 3
retry_count = 0
while retry_count < max_retries:
result = client.beta.background_tasks.retrieve(task_id=task_id)
if result.status == "completed":
return result
elif result.status == "failed":
retry_count += 1
if retry_count < max_retries:
print(f"Task failed. Retrying... ({retry_count}/{max_retries})")
# Re-submit the task
new_task = client.beta.background_tasks.create(
request_id=f"{task_id}-retry-{retry_count}",
# ... same parameters as before
)
task_id = new_task.task_id
else:
print(f"Task failed after {max_retries} retries")
return None
else:
# Still running
time.sleep(60)
return result
The point: Expect occasional failures. Build retry logic. Have a backup plan.
When NOT to Use Background Tasks
Background tasks aren’t magic. They’re not always the right choice:
- Small, quick tasks – If it takes < 1 minute, just do it interactively.
- Iterative work – If you need to ask follow-up questions or adjust course, use interactive sessions.
- Tasks needing human judgment – If Claude needs your input to proceed, you can’t use background mode.
- Real-time monitoring – If you need to watch progress and react immediately, keep it interactive.
Background tasks are for set-it-and-forget-it work. When you know exactly what you want and you don’t need to iterate.
Cost Optimization
Background tasks consume resources, and that’s a real cost. But smart optimization can cut costs dramatically:
Batch small operations: Instead of 100 separate small tasks, combine them into one larger task. One task invocation costs less than 100.
Cache results: If you’re processing the same files multiple times, cache results locally. Don’t re-process files that haven’t changed.
Set realistic timeouts: Don’t set a 2-hour timeout if the operation typically finishes in 20 minutes. Overly conservative timeouts hold resources longer than necessary.
Process only what changed: If your codebase has 10,000 files but only 50 changed, process only those 50. This is especially important for analysis tasks like security scanning or performance profiling.
Run parallel independent tasks: Instead of running 10 analysis tasks sequentially (10 hours total), run them in parallel (1 hour total). This costs the same but finishes faster.
Integration With Your Workflow
How background tasks fit into your workflow depends on your team:
Solo developer: Background tasks free you from waiting on long operations. Start a refactor, work on documentation while it runs.
Small team (5-10): Share task configurations. Build notification mechanisms so team members know when results are ready. Use shared dashboards so anyone can see what’s running.
Medium team (10-50): Formalize task definitions. Build governance around which tasks can run on which branches. Integrate with CI/CD. Start tracking costs. Assign someone to maintain task definitions.
Large team (50+): Build a full task service. Orchestrate across services. Integrate with your change management system. Track ROI on each task type. Retire low-value tasks.
Your task infrastructure should evolve with your team, not stay static.
Advanced Task Orchestration
For complex workflows, you can orchestrate multiple background tasks. Imagine migrating from PostgreSQL to MongoDB. This involves:
- Analysis: Understand schema and data volume
- Transformation: Build transformation pipeline
- Validation: Verify data integrity
- Migration: Run actual migration
- Verification: Confirm everything worked
Each of these could be a separate background task. Task 2 depends on Task 1. Task 4 depends on Task 3. Some can run in parallel. This composition pattern is powerful because it lets you build complex workflows from simple pieces.
Common Patterns
Over time, you’ll develop patterns that work well for your team. Here are some proven patterns:
The Batch and Check Pattern: Queue up many small tasks (analyze different services, test different configurations), let them run in parallel, then check results. Useful for portfolio analysis—”run this check against every service in our monorepo.”
The Cascade Pattern: One task outputs results that feed into the next task. Analysis → Design → Implementation → Validation. Each step depends on previous steps. Useful for complex transformations.
The Fan-Out Pattern: One task generates artifacts that multiple subsequent tasks process. Generate data files → multiple analysis tasks process those files → aggregate results. Useful for parallel analysis.
The Wait-and-Notify Pattern: Start a task, wait for completion with periodic checking, notify when done. User-friendly pattern that doesn’t require polling.
The Real Value
The deepest benefit of background tasks isn’t just saving time—it’s enabling possibilities that were previously infeasible. You can run analysis that would have been too expensive before. You can experiment with approaches you wouldn’t have tried. You can tackle technical debt that was too risky to touch.
When you’re not waiting for operations to complete, you can think differently. You can start experiments you wouldn’t have tried before. You can optimize things that seemed too slow to optimize.
“This refactor would take 4 hours manually” becomes “that’s a 10-minute background task.” “Running tests across the whole monorepo would take 90 minutes” becomes “that’s a 15-minute parallel task run.” Operations that were infeasible become routine.
This changes how you architect your systems. You might try more aggressive refactors knowing the validation is instant. You might experiment with different approaches knowing you can test all of them in parallel. You might optimize for clarity over performance knowing you can run comprehensive analysis on any change.
Background tasks are a leverage point. They don’t just save time—they change what feels possible to do. Your team will love it. Nobody likes waiting. When you eliminate waiting, you eliminate a major source of frustration. Developers feel more productive. They ship faster. They’re happier. That’s not just a nice side effect—it’s a fundamental improvement to the development experience. Your entire engineering organization becomes more effective as a whole.
-iNet