◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 44 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 44 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/Cowork
◈ Cowork/2026-10-17Intermediate

When a Scheduled Task Keeps Saying SUCCEEDED but Produces Nothing, Check Outbound Traffic in the First Minute

The run history is green, but the output file is still yesterday's. A short probe and a freshness check that tell you in the first minute whether outbound calls are being refused with a 403 on CONNECT.

Cowork48Scheduled Tasks14NetworkingTroubleshooting21Unattended Runs

The night I read GitHub issue #93562, I reopened the run list for my own scheduled tasks. Every row said SUCCEEDED. Not a single red line.

I still couldn't settle. The symptom in the report was exactly this: runs recorded as successful, with nothing produced.

What I'd like to say up front is that the color of the run list is not evidence that anything was produced. Below is a small probe that checks outbound traffic in the first minute, plus a second check that looks at the output file's age instead of the run record.

What the report describes

The reporter had 18 daily scheduled tasks, and from September 11, 2026 none of them produced output. The schedule still fired and every run was recorded as SUCCEEDED, but the first outbound call in each run was refused by the proxy with a 403 on CONNECT.

Their settings were network egress on and the domain allowlist set to "All domains". Hosts outside a small built-in list were still refused, and the local VM on the desktop behaved the same way. Several other people in the thread said they were seeing it too.

That is as far as I can vouch for. I can't tell you the cause or when it will be fixed from the issue text, and the issue was still open when I fetched it.

What matters more to me than the cause is how many minutes it would take me to notice if my own tasks ended up in that state.

A probe for the first minute

As an indie developer, if I hand wallpaper-app chores to a scheduled task, the first step is almost always a call to some external API or site. If that's blocked, every later step is wasted motion. So I check "can I get out at all?" at the very top and always leave one line behind.

#!/usr/bin/env bash
# egress_probe.sh — log one line per host; exit 1 if any host is unreachable
# usage: ./egress_probe.sh https://api.github.com https://example.com
set -u
LOG="${PROBE_LOG:-./egress_probe.log}"
blocked=0
 
probe() {
  local url="$1" out code connect rc
  # %{http_connect} is the proxy's response to CONNECT (000 when going direct)
  out=$(curl -sS -m 15 -o /dev/null -w '%{http_code} %{http_connect}' "$url" 2>&1)
  rc=$?
  code=${out%% *}; connect=${out##* }
  if [ "$rc" -eq 56 ] || [ "$connect" = "403" ]; then
    echo "BLOCKED(proxy) rc=$rc connect=$connect $url"; return 1
  elif [ "$rc" -eq 6 ]; then
    echo "NO-DNS rc=$rc $url"; return 1
  elif [ "$rc" -ne 0 ]; then
    echo "FAIL rc=$rc $url"; return 1
  fi
  echo "OK http=$code connect=$connect $url"
}
 
echo "== $(date -u +%FT%TZ) start (${#@} hosts)" >> "$LOG"
for u in "$@"; do
  line=$(probe "$u") || blocked=$((blocked+1))
  echo "$line" | tee -a "$LOG"
done
echo "== end blocked=$blocked" >> "$LOG"
[ "$blocked" -eq 0 ]

Three notes on why it's written this way.

  • The local declaration and the assignment are on separate lines so that rc=$? keeps curl's exit code. Write local out=$(curl ...) on one line and $? holds the result of local itself, which is always 0, so neither 56 nor 6 would ever show up.
  • curl -w '%{http_connect}' returns the proxy's answer to CONNECT. The CONNECT tunnel failed, response 403 message in the report corresponds to curl exit code 56, so either signal lets you say "the proxy refused this" with confidence.
  • The final [ "$blocked" -eq 0 ] returns exit 1 if anything was blocked. I add one sentence to the task prompt: "If the probe fails, write BLOCKED and stop. Don't pretend to have produced output."

Reading the result

I sort the probe's lines into three groups.

Line starts withMeaningFirst thing to check
BLOCKED(proxy)Proxy refused CONNECT (exit code 56 or connect=403)The allowlist in your network settings, and the issue's latest status
NO-DNSName did not resolve (exit code 6)Spelling of the host, and whether you're using a non-HTTP(S) port
FAIL / OK with http 4xx or 5xxTraffic got through; the remote side answered badlyAn outage at the service, or an expired credential

The reporter also notes that IMAP on port 993 didn't even resolve. The allowlist only covers HTTP(S) over CONNECT, so mail ports and SSH are a separate matter. Mix those up and you can spend a long time wondering why everything works after switching to "All domains" except IMAP.

Look at the output's age, not the run record

The probe only tells you whether you can get out today. To notice a failure that happened overnight, the most reliable thing is to read the output file's modification time directly.

#!/usr/bin/env bash
# check_fresh.sh — is the output file recent enough? (ignores the run record's color)
# usage: ./check_fresh.sh <file> [max age in hours]   e.g. ./check_fresh.sh daily_report.md 26
f="${1:?file required}"; max_h="${2:-26}"
[ -e "$f" ] || { echo "MISSING $f"; exit 2; }
now=$(date +%s)
# works with both GNU and BSD stat
mtime=$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f")
age_h=$(( (now - mtime) / 3600 ))
if [ "$age_h" -gt "$max_h" ]; then echo "STALE ${age_h}h > ${max_h}h $f"; exit 1; fi
echo "FRESH ${age_h}h $f"

I use 26 hours rather than 24 so a run that drifts a little doesn't raise a false alarm. When I made files with different timestamps and ran it, a fresh file printed FRESH 0h, a file from 40 hours earlier printed STALE 40h > 26h with exit code 1, and a missing file printed MISSING with exit code 2.

Three exit codes change the morning question from "is there a red row?" to "which kind of red is it?"

What I could measure, and what I couldn't

On the afternoon of October 17, 2026, I ran the probe's curl against seven hosts from the Cowork local VM.

HostResponse
api.github.com200
raw.githubusercontent.com301
example.com200
news.google.com302
www.tagesschau.de200
code.claude.com302
platform.claude.com200

So I did not reproduce the 403 from the report. The connect column stayed at 000, which means I saw no sign of a proxy in the path. A host that doesn't exist returned exit code 6, which is the only case where I saw a NO-DNS line.

That means what a BLOCKED line actually looks like rests on the log in the issue (curl: (56) CONNECT tunnel failed, response 403). I didn't see that myself, and I'd rather say so plainly.

One thing to add today

Put the probe once at the top of a scheduled task. One or two hosts, the ones the task calls first, are enough.

I've written about getting tripped up by a shared folder in The lock I left in a shared folder shut out every run after the first, and about a check that could no longer fail in Until I planted a failing sample, my unattended checks had never once failed. Both are about doubting a green record.

Treat the run color as a hint, and look at the output's timestamp first thing in the morning. I intend to keep that one line however the platform underneath changes.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

◈ Cowork2026-09-24
Will Your Cowork Scheduled Task Still Run Next Saturday?
A definition that runs once and stops, two fire times packed into one task, a weekday that quietly emptied after an edit. Cowork schedules can go missing without leaving a single log line. Here is a working audit that compares what you defined with what you meant.
◈ Cowork2026-10-01
Before You Trim a Scheduled Task's Runbook, Count How Many Times It Gets Re-read
I cut 13 KB from a scheduled task's preloaded docs, and recounted by re-reads it was worth about 1.3 tool calls. Here is the ledger that counts re-reads, and a shell helper that records bytes during a run.
◈ Cowork2026-09-18
How I decide between Cowork and Claude Code: by where approvals land
Cowork and Claude Code run the same Claude, so I stopped comparing features and started sorting work by one question: can a human approval interrupt this step, or not? Here is the line I draw and the code I keep on the unattended side.