Skip to content

Long sessions can exhaust 256-FD limit and break shell tools with ProcessFdQuotaExceeded #122

Closed

Description

@pranavp311

In long-context/long-running agent sessions, shell tools can start failing with Zig error.ProcessFdQuotaExceeded. This reproduces on the latest upstream release, not only the dev checkout. Observed symptoms from saved subagent logs: every bash invocation, even simple pwd/ls, failed immediately with ProcessFdQuotaExceeded.\n\nInvestigation notes:\n- Zig maps this to the process file descriptor quota being exhausted (EMFILE / too many open files).\n- macOS default soft ulimit is often 256; current harness child shells report ulimit -n = 256.\n- graff spawns many subprocesses with piped stdout/stderr (bash, codedb, webfetch/kuri, zigpatch, git auto-commit, subagents/workflows). Concurrent subagents/workflows and background jobs can multiply FD use quickly.\n- The latest code does not appear to raise the process FD limit at startup.\n- Background jobs are retained in g_jobs until session exit even after completion; their child/process/future resources should be audited for FD/resource retention.\n\nSuggested fixes:\n1. At startup, call std.process.raiseFileDescriptorLimit (or equivalent) to raise the soft nofile limit where possible, and log the resulting limit.\n2. Add backpressure/global concurrency caps around subprocess-spawning tools, especially workflow/subagent fan-out.\n3. Audit all subprocess paths for closing/reaping stdio FDs; completed background jobs should be reapable/collectable before session exit.\n4. Improve the error message for ProcessFdQuotaExceeded with actionable advice (raise ulimit/restart session/kill background jobs).\n\nTemporary user workaround: start graff from a shell with a higher limit, e.g. ulimit -n 4096 && graff ..., or adjust macOS launch limits for GUI-launched apps.

Activity

  1. pranavp311 commented on Jul 10, 2026

    @pranavp311
    ContributorAuthor

    related zig issue linked here ziglang/zig#23273 lel

  2. justrach commented on Jul 11, 2026

    @justrach
    Owner

    Status audit @ v0.0.200: still open — none of the four suggested fixes landed — but the investigation narrows it usefully.

    Confirmed unfixed:

    • No rlimit handling anywhere in src/ (and no rlimit symbols in the shipped binary). Empirically: under ulimit -n 256, a child bash inside graff still reports 256.
    • The error path still prints a bare error: ProcessFdQuotaExceeded (generic failure() in tools.zig:352-355); the background-job spawn path would give actively wrong advice for this error (exec.zig:137-139 suggests running it in the background).
    • No new fan-out caps (the 8-task workflow cap and no-nested-subagents rule predate this issue; per-turn tool fan-out is uncapped, agent_tools.zig:118-123).

    Good news from the audit — the leak theory is disproven:

    • Completed background jobs hold no FDs: jobPump always ends in child.kill/child.wait, and Zig 0.16's child cleanup closes all parent-side pipe FDs.
    • Empirically: graff's own FD count stays flat at 11 across 15 model turns, and 150 sequential bash spawns under a 256-FD limit all succeeded. So the failure mode needs real parallel fan-out pressure (concurrent tools/subagents/MCP servers/background jobs in one turn), not long-session accumulation.

    The fix is now one line: Zig 0.16.0 std ships std.process.raiseFileDescriptorLimit() (process.zig:647, with correct Darwin OPEN_MAX clamping — the API discussed in ziglang/zig#23273). Call it early in main() before subcommand dispatch, optionally log the resulting limit, and special-case ProcessFdQuotaExceeded in the tool-failure message with actionable advice.

  3. added a commit that references this issue on Jul 12, 2026
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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions