Building your first Claude Code agent: an Australian small business case study
Step-by-step build of a real agent that audits a Shopify store nightly, flags inventory issues and posts to Slack. Full config + prompts.
We’ll build a real agent that runs nightly on a server, audits a Shopify store’s inventory, and posts findings to Slack. Full config + cron + permissions. Total setup time: ~90 minutes. Monthly run cost: under $10 AUD.
Most “first agent” tutorials are toys. This one is the agent we actually run for an Australian Shopify skincare brand every night at 23:00 AEST. It pulls inventory state from Shopify, runs sanity checks (stock at zero, prices changed, products with no image), and posts a summary to a Slack channel.
End-to-end build below. You can adapt this template to any nightly-audit workflow.
What the agent does
Every night at 23:00 AEST:
- Connects to the Shopify Admin API via MCP
- Pulls every product + inventory level (cached from Shopify, fast)
- Runs three checks:
- Products with stock at zero or below
- Products with no image
- Products whose price changed in the last 24 hours
- Posts a summary to Slack channel
#overnight-inventory - If anything urgent (stock zero for a top-10 SKU), DMs the operations lead
If the run is clean, it posts a one-line “All clear” so we know it ran. If it failed entirely (Shopify down, Anthropic API hiccup), we get a separate alert via UptimeRobot watching the Hetzner-side log file.
Prerequisites
- Claude Code installed (we run on a Hetzner Ubuntu 24.04 VPS, same as our Telegram bot)
- Anthropic API key with at least $20 USD credit and a budget alert at $40
- Shopify custom app with read scopes for products, orders, inventory
- A Slack incoming webhook URL
- 90 minutes
Step 1: directory + Claude config
mkdir -p /opt/agents/skincare-inventory
cd /opt/agents/skincare-inventory
CLAUDE.md:
# Agent: nightly inventory audit
You are an unattended agent. You run once at 23:00 AEST every night.
You have no human in the loop. Speak in clear, concise prose suitable for Slack.
## Mission
1. Pull the client's current inventory state via Shopify MCP.
2. Run the three checks defined in checks.md.
3. Post a Slack summary via the slack_webhook tool.
4. Exit cleanly.
## Constraints
- Australian English.
- AUD pricing.
- Do not make changes to Shopify (read-only).
- If you cannot reach Shopify or Slack, write the error to error.log and exit 1.
- Do not call any tool more than 3 times for the same purpose.
## Output format
Slack message:
**🌙 client overnight audit, DD/MM/YYYY**
- Out of stock: <list of SKUs or "none">
- No image: <list or "none">
- Price changed in last 24h: <list with old → new or "none">
End with "All checks complete." or "Issues found, investigate."
checks.md:
# Inventory audit checks
## Check 1: Out of stock
For every product in the store, find any variant where inventory_quantity <= 0 AND track_inventory is true. List SKU + product title.
## Check 2: No image
For every active product, check that images.length > 0. List any with zero images.
## Check 3: Price changed
For every product, compare current price to the price recorded in shopify-prices-yesterday.json (created by the previous run). List any that changed, with the previous and new price in AUD.
After audit, write today's prices to shopify-prices-today.json (the wrapper script renames it to -yesterday.json).
Step 2: MCP config
.mcp.json:
{
"mcpServers": {
"shopify": {
"command": "npx",
"args": ["-y", "@shopify/mcp-server-storefront@latest"],
"env": {
"SHOPIFY_STORE_DOMAIN": "your-store.myshopify.com",
"SHOPIFY_ACCESS_TOKEN": "shpat_REDACTED"
}
},
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack-webhook"],
"env": {
"SLACK_WEBHOOK_URL": "https://hooks.slack.com/services/..."
}
}
}
}
Step 3: settings + permissions
.claude/settings.json:
{
"permissions": {
"allow": [
"mcp:shopify:*",
"mcp:slack:post",
"Read",
"Write(*.json)",
"Bash(date:*)"
],
"deny": [
"Bash(rm:*)",
"Bash(curl:*)",
"WebFetch"
]
}
}
Only the tools the agent needs. Explicit deny on dangerous shell commands and on WebFetch (everything goes through MCP).
Step 4: the wrapper script
run.sh:
#!/usr/bin/env bash
set -euo pipefail
cd /opt/agents/skincare-inventory
# Rotate prices file (today → yesterday) for the next run's diff
[ -f shopify-prices-today.json ] && mv shopify-prices-today.json shopify-prices-yesterday.json
# Cap turns + tokens to prevent runaway
export ANTHROPIC_MAX_TOKENS=120000
# Run headless. --print exits after one cycle of agentic work.
claude --print --max-turns 25 "$(cat <<'EOF'
Run the nightly inventory audit per CLAUDE.md. Use the checks defined in checks.md.
Post the result to Slack. Write today's prices to shopify-prices-today.json.
EOF
)" > last-run.log 2>&1 || {
echo "Inventory audit failed at $(date)" >> error.log
curl -s -X POST $SLACK_WEBHOOK_URL -H 'Content-Type: application/json' \
-d '{"text":"⚠️ the nightly inventory audit FAILED, check /opt/agents/skincare-inventory/error.log"}'
exit 1
}
chmod +x run.sh.
Step 5: cron entry
crontab -e
Add:
# the nightly inventory audit, 23:00 AEST nightly (13:00 UTC during AEDT, 13:00 UTC during AEST too if server is in UTC)
0 13 * * * /opt/agents/skincare-inventory/run.sh
The server is in UTC. 23:00 Melbourne time is 13:00 UTC during AEDT and 13:00 UTC during AEST (Melbourne uses AEDT October-April, AEST otherwise). For client deployments we accept the one-hour drift twice a year; if you need precision, use a wrapper that calculates the local-time offset.
Step 6: smoke test
./run.sh
You should see a Slack message in #overnight-inventory within 60-90 seconds. If you don’t, check last-run.log, it’ll show what Claude actually did.
Step 7: budget guardrails
In Anthropic Console:
- Set a hard spending cap of $30 USD/month on the API key this agent uses.
- Email alert at 50% and 80%.
- Use a dedicated API key for this agent so spend is attributable.
What it costs to run
Each nightly run does about 15-30 tool calls, ~40k input tokens, ~5k output. With caching, that’s roughly $0.08 AUD per run. 30 runs/month = $2.40 AUD. Real bills: we average $4-6 AUD/month including a few re-runs and occasional poking.
Common failures + fixes
- Shopify rate limit. Reduce concurrent product reads in your prompt. Tell Claude to fetch in batches of 50.
- Slack webhook silently rejected. Check the webhook URL is valid; Slack rotates these without warning if compromised.
- Claude hits the turn cap. Increase
--max-turnscautiously. Or tighten the prompt so the work fits. - Output drifts in format. Reinforce the format requirement in CLAUDE.md and add an example.
Where to take this next
Once the inventory agent is reliable, the natural follow-ons are:
- A morning agent that summarises overnight orders + flags any suspicious ones
- A weekly agent that runs a cohort revenue analysis and writes the result to a Notion page
- A monthly agent that drafts the marketing report from Meta Ads + Shopify data
Same template each time: CLAUDE.md → MCP → settings → wrapper → cron. The first one is the hardest; subsequent agents take 30-45 minutes.
If you want us to build the first one for you and then teach you the pattern, that’s literally the Quick Start package.
Common questions
Can Claude Code run unattended on a server?
How do I stop it from running up a huge bill?
Does the agent need to be on my laptop or can it live on a server?
Want this built for your business?
Book a free 30-minute AI audit. We'll map your business and show you exactly which systems we'd build first. No pitch deck, no scoping fee.
Book my free AI audit