Skip to content

[CLI] specstory run claude busy-waits at 20-50% CPU per instance when child process is idle (waiting for user input) #207

Description

@mickaelperrin

Summary

When using specstory run claude with multiple concurrent sessions, the specstory parent process consumes 20–50% CPU per instance even when the wrapped claude child process is completely idle (waiting for user input at the prompt).

This appears to be a busy-wait / active polling loop in specstory's I/O monitoring of the child process, rather than using blocking I/O primitives (select/poll/epoll/kqueue).

Environment

  • specstory version: 1.12.0
  • OS: Darwin arm64 (macOS)
  • claude version: 2.1.83 (Claude Code)
  • invocation: specstory run claude -c /path/to/claude --settings /path/to/settings.json --silent --no-version-check --local-time-zone

Reproduction

  1. Open multiple terminal sessions (20+) each running specstory run claude
  2. Leave all sessions idle at the Claude prompt (waiting for user input)
  3. Observe CPU usage with ps aux | grep specstory

Observed Behavior

With 21 concurrent sessions all idle (claude children waiting for input):

# specstory CPU% specstory state claude child CPU% claude child state
1 46.0 R+ 0.0 S+
2 42.4 R+ 0.0 S+
3 31.2 R+ 0.0 S+
4 22.8 R+ 0.0 S+
5 35.1 R+ 0.0 S+
6 37.9 R+ 0.0 S+
7 30.2 R+ 0.0 S+
8 34.1 R+ 0.0 S+
9 34.8 R+ 0.0 S+
10 38.2 R+ 0.0 S+
11 51.9 R+ 0.0 S+
12 23.4 R+ 0.0 S+
13 20.1 R+ 0.0 S+
14 37.8 R+ 0.0 S+
15 25.7 R+ 0.0 S+
16 41.9 R+ 0.0 S+
17 30.1 R+ 0.0 S+
18 40.4 R+ 0.0 S+
19 46.0 R+ 0.0 S+
20 42.4 R+ 0.0 S+
21 35.1 R+ 0.0 S+

Totals:

  • specstory total CPU: ~746%
  • claude children total CPU: ~2.9%
  • All specstory processes: R+ (running, foreground) — actively consuming CPU
  • All claude children: S+ (sleeping, foreground) — idle, waiting for input

Expected Behavior

When the child process is idle (waiting at its input prompt), specstory should block on I/O (using select, poll, epoll, or kqueue) consuming ~0% CPU, exactly like the child itself does.

Impact

  • Machine unusable with 20+ concurrent sessions (fans spinning, battery drain)
  • ~750% wasted CPU from idle monitoring
  • No workaround available — the specstory CLI has no --poll-interval or --no-poll flags
  • Disabling specstory entirely (NO_SPECSTORY=1) is the only current workaround, but loses transcript history

Suggested Fix

Replace the busy-wait polling loop with a proper blocking I/O wait (kqueue on macOS / epoll on Linux) when monitoring the child process's stdout/stderr. The specstory process should only wake up when there is actual data to read from the child.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions