Build a Proactive Agent Workflow with Claude Code
Turn Claude Code from a reactive assistant into a proactive teammate using Routines — scheduled, event-driven agents that read your repo and open PRs before you've touched your laptop.
This lesson is original educational writing based on this video by Anthropic (published May 20, 2026). All credit for the original content goes to the creators.
1. Reactive vs proactive: a fundamental distinction
Every Claude Code session you have ever had is reactive. You type a prompt, Claude acts, you read the result, you type again. The loop only advances when you push it forward. That model is powerful, but it has a ceiling: Claude can only work when you are working.
Proactive agents flip this. Instead of waiting for your prompt, they watch for a condition and act when that condition is true — whether you are at your desk, asleep, or on a flight. Claude Code calls these Routines.
A Routine is an autonomous Claude Code session that:
- Starts on a trigger (a schedule, an event, or an API call)
- Gathers context from the repository and any connected data sources
- Executes a task using the same tools Claude Code has in interactive mode
- Produces an artifact — a PR, a report, a comment — and stops
The result lands in your repository ready for your review. You shifted from being the driver to being the approver.
2. The anatomy of a Routine
Every Routine — no matter how simple or complex — is defined by three decisions. Getting these three things right is the whole design job.
Trigger
When should the Routine fire? The three trigger categories are:
| Trigger type | Example | Best for |
|---|---|---|
| Schedule (cron) | Every weekday at 08:00 | Daily reports, recurring reviews |
| Event | On push to main | Post-merge summaries, lint checks |
| On-demand | /schedule run standup | Manual kicks, demos, one-offs |
A scheduled trigger takes a standard cron expression. An event trigger hooks into repository webhooks or CI signals. On-demand triggers run immediately and are useful when you want a Routine’s structured output but are initiating it yourself.
Context
What should the Routine read before it starts working? Context is the most important lever you have. A Routine with rich, targeted context produces dramatically better output than the same Routine with shallow context.
Typical context sources:
- Git history —
git log --since=yesterday,git diff HEAD~1 - Open issues and PRs — fetched via the GitHub CLI or MCP servers
- Configuration files — package.json, CI configs, CLAUDE.md
- External data — error logs, monitoring dashboards, calendar events
The ROUTINE.md file (described below) is where you specify which context to gather and how.
Steering
How does the Routine know what good output looks like? Steering is the set of instructions that guide the Routine’s judgment: what to prioritize, what to skip, what format to use for its output, and what to do when it is uncertain.
Steering lives in the ROUTINE.md system prompt. It is different from a one-off interactive prompt because it must anticipate all the cases you will not be there to handle.
3. ROUTINE.md: the Routine’s memory
A Routine does not have you to ask questions mid-run. The ROUTINE.md file compensates for that. It is a Markdown document committed to your repository that serves as the Routine’s persistent instructions — the equivalent of a CLAUDE.md but scoped to a single automated job.
A well-written ROUTINE.md answers four questions:
- What is the goal? One sentence describing the desired end state.
- What data should I read? Explicit commands or file paths for gathering context.
- What should the output look like? Format, length, conventions.
- What are the guardrails? What the Routine must never do, and what to do when uncertain.
Here is a minimal example for a daily standup Routine:
# Daily Standup Routine
## Goal
Create a draft PR each morning that summarises what changed in the main branch
since yesterday and flags anything that needs team attention.
## Context to gather
- Run `git log --since=yesterday --oneline main`
- Run `git diff $(git log --since=yesterday --format="%H" main | tail -1)..HEAD`
- Read open PR titles: `gh pr list --state open --json title,number,author`
## Output format
Open a pull request titled "Standup: <date>" against main with a body that contains:
- A bullet list of commits grouped by author
- A "Needs attention" section for anything touching shared config or migration files
- A "No action needed" section for everything else
## Guardrails
- Do NOT merge the PR. Only open it as a draft.
- Do NOT push directly to main.
- If git log returns nothing, open a PR with body "No commits since yesterday."
- If a git command fails, stop and write the error to a file called ROUTINE_ERROR.md.
The guardrails section is the most important and most often skipped part. Explicit negative constraints prevent the Routine from taking irreversible actions when something unexpected happens.
4. Scheduling a Routine with /schedule
Once your ROUTINE.md is in place, scheduling the Routine takes one command inside Claude Code:
/schedule "Run the daily standup routine" --cron "0 8 * * 1-5" --routine ROUTINE.md
Breaking this down:
"Run the daily standup routine"— the task description Claude receives as its starting prompt--cron "0 8 * * 1-5"— standard cron syntax: 08:00 UTC on Monday through Friday--routine ROUTINE.md— the file whose contents are injected as the system context
You can also trigger a Routine manually at any time for testing:
/schedule run standup
And list all configured Routines:
/schedule list
To stop a Routine from running again:
/schedule delete standup
Viewing Routine output
After a Routine fires, you will see its artifact in the repository — a PR, a commit, or whatever your ROUTINE.md specifies. Claude Code also keeps a log of each run accessible via the Anthropic console, where you can inspect every tool call the agent made, the context it read, and any errors it encountered.
5. Designing effective proactive agents
Moving from a working Routine to a great one requires thinking through a few recurring failure modes.
Stale context produces stale output
A Routine that only reads git log will miss issues filed today, comments left on open PRs,
or a failing CI run. Think about everything a human teammate would check before writing the
standup, then make sure the Routine reads all of it.
For richer context, combine git with the GitHub CLI and any MCP servers your team uses:
## Context to gather
- git log --since=yesterday --oneline main
- gh issue list --state open --label "bug" --json title,number
- gh run list --workflow=ci.yml --limit=5 --json conclusion,headBranch
Idempotency prevents duplicate artifacts
If a Routine fires twice because of a scheduling edge case, it should not open two identical PRs. Add an idempotency check to your guardrails:
## Guardrails
- Before opening a PR, run `gh pr list --search "Standup: $(date +%Y-%m-%d)" --json number`.
If a result exists, add a comment to that PR instead of opening a new one.
Scope creep is the main failure mode
The longer a Routine’s task description and the richer its context, the more likely it is to wander. A Routine told to “review the codebase” will do something different every day. A Routine told to “report commits to src/api/ that modify response schemas” does exactly the same thing every time.
Narrow scope → consistent output → Routine you can trust and keep.
Check your understanding
5 questions · your answers are saved in this browser only
-
1. What is the fundamental difference between a reactive Claude Code session and a proactive Routine?
-
2. Which three decisions define the design of any Routine?
-
3. In the /schedule command, what does the --routine flag do?
-
4. Why is an explicit "Guardrails" section the most important part of a ROUTINE.md?
-
5. What is the best practice before activating a cron schedule for a new Routine?
6. Real-world Routine patterns
The standup Routine is a learning vehicle. Here are four patterns that appear in real production workflows:
Dependency drift report
Fires weekly, reads package.json and package-lock.json, compares installed versions
against the latest published versions on npm, and opens a PR that upgrades any package more
than one major version behind. The guardrails exclude packages that are pinned in a
no-upgrade.txt file and cap the PR at five packages per run to keep diffs reviewable.
Test coverage gate
Fires on every push to a feature branch via a repository webhook. Reads the coverage report
from the last CI run, compares it against a threshold in .claude/coverage-policy.json, and
posts a PR comment. If coverage dropped, it lists the uncovered lines and suggests which test
cases would close the gap. It never modifies tests directly — it only suggests.
Security advisory scanner
Fires nightly. Reads package.json and runs npm audit --json, then cross-references the
advisory list against your project’s dependencies. Opens a draft PR for each high-severity
advisory with a patch already applied. Guardrail: only patches devDependencies automatically;
production dependency patches are flagged for human review.
Release notes generator
Fires when a tag matching v* is pushed. Reads git log since the previous tag, groups
commits by conventional commit prefix (feat:, fix:, chore:), and opens a PR that adds
a CHANGELOG.md entry. The output format mirrors the existing CHANGELOG style — the Routine
reads three previous entries before writing the new one.
Build it yourself
Follow these exact steps to reproduce it yourself · estimated time: ~25 min
Prerequisites
- Claude Code installed and authenticated (`claude --version`)
- A git repository on GitHub with at least one recent commit
- GitHub CLI installed and authenticated (`gh auth status`)
- Repository access to open pull requests
Step 1 — Create the .claude directory
mkdir -p .claudeAll Claude Code configuration for this project will live here.
Step 2 — Write the ROUTINE.md
Create a file called ROUTINE.md at your project root:
# Daily Standup Routine
## Goal
Each weekday morning, open a draft pull request that summarises what changed in
main since yesterday and highlights anything that needs team attention.
## Context to gather
Before writing anything, run these commands and read their output:
1. `git log --since="yesterday 00:00" --until="today 00:00" --oneline main`
2. `git diff $(git log --since="yesterday 00:00" --format="%H" main | tail -1)..HEAD -- . ':(exclude)package-lock.json'`
3. `gh pr list --state open --json number,title,author,reviewDecision`
4. `gh run list --workflow=ci.yml --limit=3 --json conclusion,headBranch,createdAt`
If any command fails, note the failure in the PR body and continue.
## Output format
Open a pull request titled exactly: "Standup: YYYY-MM-DD" (today's date).
Target branch: main.
PR body sections:
- **What shipped** — bullets from git log, grouped by author
- **Open PRs** — list with review status
- **CI** — last three runs with pass/fail
- **Needs attention** — anything touching migrations, shared config, or CI workflows
## Guardrails
- Open the PR as a DRAFT. Never merge.
- Never push directly to main.
- Before opening, check: `gh pr list --search "Standup: $(date +%Y-%m-%d)" --json number`
If a PR already exists today, add a comment to it instead of opening a new one.
- If git log returns no commits, open the PR with body "No commits since yesterday."
- Maximum one PR opened per run.Step 3 — Test the Routine manually
Inside a Claude Code session in your repository, run:
/schedule run standup --routine ROUTINE.mdClaude Code will execute the Routine immediately. Watch the terminal output to see which tools it calls.
Step 4 — Inspect the artifact
Go to your repository on GitHub and find the newly opened draft PR titled “Standup: <today’s date>”. Read through it carefully:
- Are the commits grouped correctly?
- Is the “Needs attention” section accurate?
- Is the format right?
If anything is wrong, edit ROUTINE.md and run /schedule run standup again. Repeat until
the output matches what you would write by hand.
Step 5 — Enable the cron schedule
Once the output is right, activate the schedule:
/schedule "Run the daily standup routine" --cron "0 8 * * 1-5" --routine ROUTINE.md --name standupThis fires the Routine at 08:00 UTC Monday through Friday. Adjust the cron expression to match your team’s timezone:
| Target time | Cron expression |
|---|---|
| 08:00 UTC | 0 8 * * 1-5 |
| 08:00 EST (UTC-5) | 0 13 * * 1-5 |
| 08:00 CET (UTC+1) | 0 7 * * 1-5 |
| 08:00 PST (UTC-8) | 0 16 * * 1-5 |
Step 6 — Verify the schedule
List all active Routines to confirm registration:
/schedule listYou should see standup with status active and the next scheduled run time.
Step 7 — Monitor the first automated run
The morning after enabling the schedule, check GitHub for the draft PR. Also check the Anthropic console (console.anthropic.com) under the Claude Code section to view the run log — every tool call the Routine made, the context it gathered, and any errors.
Step 8 — Commit the ROUTINE.md
git add ROUTINE.md
git commit -m "Add daily standup Routine"
git pushCommitting the ROUTINE.md means every team member who clones the repo can activate the same
Routine immediately with /schedule run standup --routine ROUTINE.md.
Expected result: A draft PR appears each weekday morning titled “Standup: <date>” with
an accurate summary of the previous day’s activity. If no PR appears after the first
scheduled run, check /schedule list for error state and review the run log in the console.
Where to go next
- Watch the original video for a live end-to-end demo of a Routine being built and triggered.
- Learn about Claude Code hooks to combine deterministic lifecycle guards with your proactive Routines.
- Explore headless mode (
claude -p) for cases where you need proactive automation driven by external scripts rather than the/schedulecommand.