Scheduled task session hangs forever on unattended permission prompt, then per_task_limit silently starves all future runs

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 15, 2026

Summary

In the macOS desktop app, a scheduled task session that hits a tool permission prompt while running unattended hangs forever (no timeout, no failure state). Worse, the hung session still counts against the task's dispatch limit, so the scheduler then logs per_task_limit (active=1, limit=1) and silently skips every subsequent scheduled run of that task until a human interacts with the app and un-freezes the zombie session. One unanswered prompt at 2:49 AM turned into three consecutive nights of a broken nightly job, in two different-looking failure modes.

Environment

  • Claude desktop app on macOS (Darwin 25.6.0, Apple Silicon)
  • Scheduled tasks feature (CCDScheduledTasks), recurring cron task running Bash/Edit tools against a local Obsidian vault
  • Machine never sleeps (pmset: sleep 0), app running continuously

Repro / what happened

  1. A recurring scheduled task (cron 47 2 * * *) starts at ~2:49 AM and, ~40s in, issues a Bash pipeline not covered by any allowlist (grep ... | awk ... | sort | uniq -c | sort -rn).
  2. The harness emits a tool permission request. Nobody is at the machine (it's 2:49 AM — the entire point of a scheduled task).
  3. The session freezes mid-tool-call indefinitely. The transcript JSONL just stops after the tool_use entry. Hours later, on the next user interaction with the app, the pending request aborts: Tool permission request failed: AbortError: Tool permission stream closed before response received (night 1), or the prompt gets answered by the user and the session resumes and keeps executing its 28-hour-old instructions (night 2 → collided with the recovery jobs that had since run).
  4. Meanwhile the frozen session still counts as active, so the next night's scheduled run never dispatches. main.log shows, every minute for hours:
[CCDScheduledTasks] Skipping dispatch for nightly-task-triage: per_task_limit (active=1, limit=1)
  1. When user interaction finally released the zombie at ~7:26 AM, the scheduler immediately fired the missed run:
[CCDScheduledTasks] Spawning new session for scheduled task nightly-task-triage { cronExpression: '47 2 * * *', fireAt: undefined, lastRunAt: '2026-08-15T11:26:44.440Z', missed: '2026-08-15T06:47:00.000Z' }

Related log lines from the auto-approval layer:

[CCDScheduledTasks] Not auto-approving "Bash" in scheduled task "triage-watchdog": rule(s) not in stored approvals: Bash (stored count=1)
[CCDScheduledTasks] Not auto-approving "Bash" in scheduled task "nightly-task-triage": no suggestions on request

Expected behavior

Some or all of:

  • A permission request raised by a scheduled (unattended) session should time out after a bounded period and fail the tool call (letting the session handle the denial or end), rather than suspending the session forever.
  • A session frozen on a permission prompt should not count as "active" against per_task_limit indefinitely — or at minimum the skip should surface as a visible failure (notification / task status) instead of a silent minute-by-minute log line.
  • A resumed session whose pending prompt is finally answered hours later probably should not silently continue a long-stale scheduled run (it collided with the recovery jobs that had run in the meantime).

Workarounds we applied (in case useful to others)

  • Project-level permissions.allow rules in .claude/settings.json covering every read-only command the scheduled task uses, so it never prompts.
  • A lock-directory protocol inside the task prompts (atomic mkdir + owner token + staleness takeover) so late-resumed zombie runs stand down instead of double-writing.

View original on GitHub ↗