Skip to content

Windows: SKILL.md's stdin invocation makes parallel extraction always fail (spawn re-imports '<stdin>'), and the warning blames a __main__ guard the caller cannot add #3669

Description

@kevinishii-spec

On graphifyy 0.9.64, Windows 11, Python 3.14 (uv tool install). Hit while running the shipped /graphify skill on a 427-file Python repo — so this reproduces on the skill's own happy path, not a custom invocation.

What happens

Part A of SKILL.md (and references/-driven steps) instructs the agent to run library code by piping a heredoc into the interpreter:

@'
...
result = extract(code_files, root=Path('.').resolve(), cache_root=Path('.'))
...
'@ | & (Get-Content graphify-out\.graphify_python) -

extract() then starts a process pool. On Windows the start method is spawn, so each child re-imports the parent's __main__ by path — and that path is <stdin>. Every worker dies before doing any work:

File "...\multiprocessing\spawn.py", line 297, in _fixup_main_from_path
  main_content = runpy.run_path(main_path, run_name="__mp_main__")
OSError: [Errno 22] Invalid argument: 'C:\...\StoryForge\<stdin>'

repeated once per worker, then:

warning: parallel extraction failed (BrokenProcessPool); falling back to sequential.
On Windows this usually means the caller is missing an `if __name__ == "__main__":` guard.
Pass parallel=False to extract() to skip the pool entirely.

Extraction then completes correctly, sequentially — 427 files, 12,594 nodes, 34,368 edges.

Why it's worth fixing

  1. The skill's mandated calling convention is incompatible with the library's parallelism on Windows. Stdin invocation is what SKILL.md tells the agent to use, and it can never satisfy spawn's re-import of __main__. So every Windows skill-driven run silently loses parallel extraction — the one path most users take.
  2. The diagnostic misdiagnoses the cause. It points at a missing if __name__ == "__main__": guard, which is the usual culprit but not this one — the caller here has no file at all to put a guard in. That sends the reader after a fix they cannot apply, and the actionable hint (parallel=False) is third in line.
  3. Multi-screen traceback wall. One full traceback per worker, interleaved and arriving before the progress output, which reads as a hard failure. On a first run against a new repo it is genuinely unclear whether anything succeeded.

Suggested fix

Detect the unusable-__main__ case up front and skip the pool without attempting it:

import __main__
_spawn = multiprocessing.get_start_method(allow_none=True) == "spawn" or sys.platform == "win32"
_main_file = getattr(__main__, "__file__", None)
if _spawn and (_main_file is None or not os.path.isfile(_main_file)):
    parallel = False   # stdin/-c/REPL: spawn children cannot re-import __main__

That turns a wall of tracebacks into a silent, correct sequential run. Worth pairing with either a parallel=False in the SKILL.md snippets, or writing the step to a temp .py file instead of piping via stdin — the latter would restore parallelism on Windows rather than just silencing the failure.

Also related: the error text says "the caller is missing an if __name__ == \"__main__\": guard" — worth widening to mention stdin/-c invocation, since that is what the shipped skill does.

Reproduced on graphifyy 0.9.64 / Windows 11 / CPython 3.14 via uv tool, running the bundled windows skill. AI-assisted report; traceback and counts copied from the actual run.

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