Skip to content

fix: use CREATE_NO_WINDOW so Windows hook miner spawns don't flash a console (#1783)#1848

Open
eldar702 wants to merge 1 commit into
MemPalace:developfrom
eldar702:fix/1783-create-no-window
Open

fix: use CREATE_NO_WINDOW so Windows hook miner spawns don't flash a console (#1783)#1848
eldar702 wants to merge 1 commit into
MemPalace:developfrom
eldar702:fix/1783-create-no-window

Conversation

@eldar702

Copy link
Copy Markdown
Contributor

What

In mempalace/hooks_cli.py, _detached_popen_kwargs() builds the
creationflags for the background miner process it spawns at hook time
(session-start / stop). On Windows it previously OR'd in DETACHED_PROCESS.
This PR replaces that one flag with CREATE_NO_WINDOW:

-        for name in ("DETACHED_PROCESS", "CREATE_NEW_PROCESS_GROUP", "CREATE_BREAKAWAY_FROM_JOB"):
+        for name in ("CREATE_NO_WINDOW", "CREATE_NEW_PROCESS_GROUP", "CREATE_BREAKAWAY_FROM_JOB"):

The function's one-line docstring summary is updated from "fully detach a Popen
child" to "give a Popen child a hidden console" to keep it honest.

Why

DETACHED_PROCESS gives the child no console at all. When that child later
spawns a console grandchild (the miner shells out), the grandchild has no
console to inherit, so Windows allocates a fresh, visible console window —
the console flash users report in #1783.

CREATE_NO_WINDOW instead gives the child a real but invisible console that
all descendants inherit, so nothing flashes.

These two flags are mutually exclusive on Win32: per the
CreateProcess docs,
CREATE_NO_WINDOW is ignored when it is OR'd with DETACHED_PROCESS.
So the fix has to replace DETACHED_PROCESS, not add to it — simply adding
CREATE_NO_WINDOW alongside the existing flag would be a no-op.

What stays intact

This change is scoped to the single creationflags flag. The rest of the
hang-fix for #1268 is untouched:

  • stdin=subprocess.DEVNULL and close_fds=True — unchanged.
  • The two spawn sites pass stdout=log_f, stderr=log_f explicitly, so miner
    output is still captured to ~/.mempalace/hook_state/… regardless of
    creationflagsstdout/stderr are not dropped.
  • CREATE_NEW_PROCESS_GROUP (signal boundary) and CREATE_BREAKAWAY_FROM_JOB
    — unchanged.
  • POSIX is unaffected: that branch uses start_new_session=True and never
    touches creationflags.

The sibling loop in daemon.py is intentionally not touched — #1783 reports
the hook-spawn console-flash path only, and the daemon miner is a separate path
out of this issue's scope.

Fixes #1783

Base branch

This PR targets develop (the repo's default branch).

AI-assisted disclosure

This change was prepared with the assistance of an AI coding agent (Claude). A
human reviewed the diff, ran the test suite locally, and verified the RED→GREEN
behavior and the Win32 flag rationale before opening this PR. The repository's
CONTRIBUTING.md anticipates agentic coding tools (see the "Git identity for
contributions" note); the commit author email is set to a real GitHub-associated
address accordingly.

Test evidence (RED → GREEN)

The existing test tests/test_hooks_cli.py::test_detached_popen_kwargs_windows
was updated first to assert the new contract (CREATE_NO_WINDOW set,
DETACHED_PROCESS not set), then the source was changed. It uses the existing
monkeypatch.setattr Windows-simulation pattern, so it runs on Linux/macOS CI.

RED — updated test run before the source swap (source still yields
DETACHED_PROCESS, value 520 = 0x208 = DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP,
missing CREATE_NO_WINDOW = 0x08000000 = 134217728):

        flags = kwargs.get("creationflags", 0)
>       assert flags & 0x08000000, "CREATE_NO_WINDOW must be set"
E       AssertionError: CREATE_NO_WINDOW must be set
E       assert (520 & 134217728)

tests/test_hooks_cli.py:1068: AssertionError
=========================== short test summary info ============================
FAILED tests/test_hooks_cli.py::test_detached_popen_kwargs_windows - Assertio...
1 failed, 1 passed, 122 deselected in 0.22s

GREEN — same test after the one-line source swap:

$ uv run pytest tests/test_hooks_cli.py -q -k detached_popen_kwargs
..                                                                       [100%]
2 passed, 122 deselected in 0.08s

Full file, after the fix:

$ uv run pytest tests/test_hooks_cli.py -q
........................................................................ [ 58%]
................s...................................                     [100%]
123 passed, 1 skipped in 9.23s

Lint/format on the changed files:

$ uv run ruff check mempalace/hooks_cli.py tests/test_hooks_cli.py
All checks passed!
$ uv run ruff format --check mempalace/hooks_cli.py tests/test_hooks_cli.py
2 files already formatted

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request replaces the use of 'DETACHED_PROCESS' with 'CREATE_NO_WINDOW' on Windows in 'mempalace/hooks_cli.py' to prevent console flashing in descendant processes while avoiding hangs, and updates the corresponding tests. The reviewer points out that this same pattern is present in 'mempalace/daemon.py' and advises against applying a one-off fix to maintain repository-wide consistency, in accordance with the rule to address such common patterns across sibling implementations in a dedicated change.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread mempalace/hooks_cli.py
if os.name == "nt":
flags = 0
for name in ("DETACHED_PROCESS", "CREATE_NEW_PROCESS_GROUP", "CREATE_BREAKAWAY_FROM_JOB"):
for name in ("CREATE_NO_WINDOW", "CREATE_NEW_PROCESS_GROUP", "CREATE_BREAKAWAY_FROM_JOB"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This change replaces DETACHED_PROCESS with CREATE_NO_WINDOW to prevent console flashing on Windows. However, the exact same pattern of using DETACHED_PROCESS is present in mempalace/daemon.py inside _detached_kwargs (line 978):

        for name in ("DETACHED_PROCESS", "CREATE_NEW_PROCESS_GROUP", "CREATE_BREAKAWAY_FROM_JOB"):

According to the repository's general rules, when addressing a common issue or pattern that is present in multiple sibling implementations across the codebase, we should avoid applying a one-off fix to a single instance. Instead, we should maintain repository-wide consistency by deferring the fix to a dedicated change that addresses all occurrences together.

References
  1. When addressing a common issue or pattern (such as unsafe SQLite URI path encoding) that is present in multiple sibling implementations across the codebase, avoid applying a one-off fix to a single instance. Instead, maintain repository-wide consistency by deferring the fix to a dedicated change that addresses all occurrences together.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for flagging this. I looked at daemon.py's _detached_kwargs, and I think the two cases are genuinely different rather than one shared pattern:

So the differing flag is intentional and load-bearing, not a one-off instance of a common pattern. I'd rather keep this PR scoped to the hook-spawn path; happy to add a short comment on the daemon side documenting why it keeps DETACHED_PROCESS if that'd help future readers.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: hook-spawned miner flashes visible console windows - DETACHED_PROCESS should be CREATE_NO_WINDOW in _detached_popen_kwargs

1 participant