Skip to content

fix(logging): alias RotatingFileHandler to concurrent-log-handler (salvage #44921) - #46794

Merged
austinpickett merged 2 commits into
mainfrom
salvage/win-log-rotation-44921
Jun 17, 2026
Merged

fix(logging): alias RotatingFileHandler to concurrent-log-handler (salvage #44921)#46794
austinpickett merged 2 commits into
mainfrom
salvage/win-log-rotation-44921

Conversation

@austinpickett

Copy link
Copy Markdown
Collaborator

Summary

Salvage of #44921 (@tuancookiez-hub) onto current main. Fixes #44873.

On Windows, stdlib RotatingFileHandler.doRollover() uses os.rename(), which fails with PermissionError [WinError 32] whenever another process holds an append-mode handle on agent.log — essentially always in Hermes, where the TUI, gateway, hy_memory server, MCP servers, and on-demand CLI commands all log from separate processes. The result: agent.log gets pinned at the 5 MiB rotation threshold and every emit spams stderr with a traceback.

Confirmed still present on main: hermes_logging.py:35 still does from logging.handlers import RotatingFileHandler, and there's no concurrent-log-handler anywhere in pyproject.toml.

What changed

  • Add concurrent-log-handler==0.9.29 as a core dependency
  • Alias its ConcurrentRotatingFileHandler as RotatingFileHandler in hermes_logging.py — it wraps the rename in a cross-process file lock (via portalocker: pywin32 on Windows, fcntl on POSIX) so only one process rotates at a time and the others wait their turn
  • The alias keeps every existing reference working unchanged: _ManagedRotatingFileHandler(RotatingFileHandler) still subclasses correctly, and all isinstance(..., RotatingFileHandler) checks still match
  • Test file uses the same alias so its autouse-fixture handler-stripping matches the CLH-backed class

Deviation from the original PR

Tidied a minor inaccuracy in the explanatory comments: the original said the lock uses "msvcrt on Windows, fcntl on POSIX", but CLH 0.9.29 actually locks via portalocker (which uses pywin32 on Windows). Updated both the module and pyproject comments. No functional change.

Test plan

  • uv lock --check → consistent (220 packages); portalocker pulled transitively (pywin32 already a core dep)
  • Import E2E: hermes_logging.RotatingFileHandler is ConcurrentRotatingFileHandler → True; _ManagedRotatingFileHandler subclasses it → True
  • pytest tests/test_hermes_logging.py → 60 passed. The 2 failures observed locally (*_group_writable) are pre-existing Windows-only failures — they assert 0o660 Unix perms with no skipif guard and fail on main too; CI runs on Linux where they pass.

Credit

On Windows, stdlib RotatingFileHandler.doRollover() uses os.rename(), which
fails with PermissionError [WinError 32] whenever another process holds an
append-mode handle on agent.log — essentially always in Hermes (TUI, gateway,
hy_memory server, MCP servers, and on-demand CLI commands all log from separate
processes). This pinned agent.log at the 5 MiB threshold and spammed stderr
with a traceback on every emit (#44873).

Add concurrent-log-handler==0.9.29 as a core dep and alias its
ConcurrentRotatingFileHandler as RotatingFileHandler in hermes_logging.py. It
wraps the rename in a cross-process file lock (via portalocker: pywin32 on
Windows, fcntl on POSIX) so only one process rotates at a time. Aliasing keeps
every existing isinstance/class-declaration reference working unchanged.

Co-authored-by: tuancookiez-hub <tuancookiez@gmail.com>
@austinpickett
austinpickett requested a review from a team June 15, 2026 17:49
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: salvage/win-log-rotation-44921 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 10949 on HEAD, 10949 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 5762 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists labels Jun 15, 2026
The initial salvage aliased RotatingFileHandler -> ConcurrentRotatingFileHandler
unconditionally, which regressed POSIX: CLH opens lazily and rotates via its own
lock path, breaking managed-mode (NixOS) group-writable perms and eager file
creation that _ManagedRotatingFileHandler depends on. CI caught it as 2 failures
in test_managed_mode_*_group_writable on Linux.

The WinError 32 bug (#44873) is Windows-specific — POSIX renames an open file
fine, so stdlib already works on Linux/macOS. Gate the swap behind
sys.platform == 'win32': Windows uses CLH, POSIX keeps stdlib RotatingFileHandler.

- hermes_logging.py: platform-conditional import.
- tests/test_hermes_logging.py: import RotatingFileHandler from hermes_logging
  (single source of truth) so the autouse fixture's isinstance checks match the
  real handler class on both platforms.
- pyproject.toml/uv.lock: mark the dep 'sys_platform == "win32"' so portalocker
  /pywin32 only ship where used.
@austinpickett
austinpickett enabled auto-merge (squash) June 15, 2026 20:01

@OutThisLife OutThisLife left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving. Clean salvage, and a meaningful improvement over the original #44921: scoping the swap to Windows only (sys.platform == 'win32' import + sys_platform == 'win32' dependency marker) is the right call —

  • The bug (os.rename WinError 32 on a file another process holds open) is Windows-specific; POSIX renames an open file fine, so stdlib already works on Linux/macOS.
  • The POSIX managed-mode (NixOS) path depends on stdlib RotatingFileHandler's exact _open()/doRollover() lifecycle for the 0o660 chmod; keeping stdlib there preserves that guarantee, which CLH's lazy-open/different-rollover would break.
  • _ManagedRotatingFileHandler stays constructor-compatible with ConcurrentRotatingFileHandler (only maxBytes/backupCount/encoding are passed, all supported), and on Windows _chmod_if_managed is a no-op since is_managed() is False there — so the super() delegation in _open/doRollover is clean.
  • ==0.9.29 matches this repo's exact-pin convention for core deps; uv.lock is regenerated with the portalocker/pywin32 tree correctly gated on sys_platform == 'win32'.
  • The test imports RotatingFileHandler from the module under test so the autouse strip fixture matches whichever class resolved, on either platform.

The Windows CLH path can't execute on the Linux CI runners, but the delegation is sound and the "Windows footguns" check passes. LGTM.

@austinpickett
austinpickett merged commit 394cdf4 into main Jun 17, 2026
32 checks passed
@austinpickett
austinpickett deleted the salvage/win-log-rotation-44921 branch June 17, 2026 20:39
xyshanren pushed a commit to xyshanren/hermes-agent-cn that referenced this pull request Jun 25, 2026
…lvage NousResearch#44921) (NousResearch#46794)

* fix(logging): alias RotatingFileHandler to concurrent-log-handler

On Windows, stdlib RotatingFileHandler.doRollover() uses os.rename(), which
fails with PermissionError [WinError 32] whenever another process holds an
append-mode handle on agent.log — essentially always in Hermes (TUI, gateway,
hy_memory server, MCP servers, and on-demand CLI commands all log from separate
processes). This pinned agent.log at the 5 MiB threshold and spammed stderr
with a traceback on every emit (NousResearch#44873).

Add concurrent-log-handler==0.9.29 as a core dep and alias its
ConcurrentRotatingFileHandler as RotatingFileHandler in hermes_logging.py. It
wraps the rename in a cross-process file lock (via portalocker: pywin32 on
Windows, fcntl on POSIX) so only one process rotates at a time. Aliasing keeps
every existing isinstance/class-declaration reference working unchanged.

Co-authored-by: tuancookiez-hub <tuancookiez@gmail.com>

* fix(logging): gate concurrent-log-handler swap to Windows only

The initial salvage aliased RotatingFileHandler -> ConcurrentRotatingFileHandler
unconditionally, which regressed POSIX: CLH opens lazily and rotates via its own
lock path, breaking managed-mode (NixOS) group-writable perms and eager file
creation that _ManagedRotatingFileHandler depends on. CI caught it as 2 failures
in test_managed_mode_*_group_writable on Linux.

The WinError 32 bug (NousResearch#44873) is Windows-specific — POSIX renames an open file
fine, so stdlib already works on Linux/macOS. Gate the swap behind
sys.platform == 'win32': Windows uses CLH, POSIX keeps stdlib RotatingFileHandler.

- hermes_logging.py: platform-conditional import.
- tests/test_hermes_logging.py: import RotatingFileHandler from hermes_logging
  (single source of truth) so the autouse fixture's isinstance checks match the
  real handler class on both platforms.
- pyproject.toml/uv.lock: mark the dep 'sys_platform == "win32"' so portalocker
  /pywin32 only ship where used.

---------

Co-authored-by: tuancookiez-hub <tuancookiez@gmail.com>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…lvage NousResearch#44921) (NousResearch#46794)

* fix(logging): alias RotatingFileHandler to concurrent-log-handler

On Windows, stdlib RotatingFileHandler.doRollover() uses os.rename(), which
fails with PermissionError [WinError 32] whenever another process holds an
append-mode handle on agent.log — essentially always in Hermes (TUI, gateway,
hy_memory server, MCP servers, and on-demand CLI commands all log from separate
processes). This pinned agent.log at the 5 MiB threshold and spammed stderr
with a traceback on every emit (NousResearch#44873).

Add concurrent-log-handler==0.9.29 as a core dep and alias its
ConcurrentRotatingFileHandler as RotatingFileHandler in hermes_logging.py. It
wraps the rename in a cross-process file lock (via portalocker: pywin32 on
Windows, fcntl on POSIX) so only one process rotates at a time. Aliasing keeps
every existing isinstance/class-declaration reference working unchanged.

Co-authored-by: tuancookiez-hub <tuancookiez@gmail.com>

* fix(logging): gate concurrent-log-handler swap to Windows only

The initial salvage aliased RotatingFileHandler -> ConcurrentRotatingFileHandler
unconditionally, which regressed POSIX: CLH opens lazily and rotates via its own
lock path, breaking managed-mode (NixOS) group-writable perms and eager file
creation that _ManagedRotatingFileHandler depends on. CI caught it as 2 failures
in test_managed_mode_*_group_writable on Linux.

The WinError 32 bug (NousResearch#44873) is Windows-specific — POSIX renames an open file
fine, so stdlib already works on Linux/macOS. Gate the swap behind
sys.platform == 'win32': Windows uses CLH, POSIX keeps stdlib RotatingFileHandler.

- hermes_logging.py: platform-conditional import.
- tests/test_hermes_logging.py: import RotatingFileHandler from hermes_logging
  (single source of truth) so the autouse fixture's isinstance checks match the
  real handler class on both platforms.
- pyproject.toml/uv.lock: mark the dep 'sys_platform == "win32"' so portalocker
  /pywin32 only ship where used.

---------

Co-authored-by: tuancookiez-hub <tuancookiez@gmail.com>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
…lvage NousResearch#44921) (NousResearch#46794)

* fix(logging): alias RotatingFileHandler to concurrent-log-handler

On Windows, stdlib RotatingFileHandler.doRollover() uses os.rename(), which
fails with PermissionError [WinError 32] whenever another process holds an
append-mode handle on agent.log — essentially always in Hermes (TUI, gateway,
hy_memory server, MCP servers, and on-demand CLI commands all log from separate
processes). This pinned agent.log at the 5 MiB threshold and spammed stderr
with a traceback on every emit (NousResearch#44873).

Add concurrent-log-handler==0.9.29 as a core dep and alias its
ConcurrentRotatingFileHandler as RotatingFileHandler in hermes_logging.py. It
wraps the rename in a cross-process file lock (via portalocker: pywin32 on
Windows, fcntl on POSIX) so only one process rotates at a time. Aliasing keeps
every existing isinstance/class-declaration reference working unchanged.

Co-authored-by: tuancookiez-hub <tuancookiez@gmail.com>

* fix(logging): gate concurrent-log-handler swap to Windows only

The initial salvage aliased RotatingFileHandler -> ConcurrentRotatingFileHandler
unconditionally, which regressed POSIX: CLH opens lazily and rotates via its own
lock path, breaking managed-mode (NixOS) group-writable perms and eager file
creation that _ManagedRotatingFileHandler depends on. CI caught it as 2 failures
in test_managed_mode_*_group_writable on Linux.

The WinError 32 bug (NousResearch#44873) is Windows-specific — POSIX renames an open file
fine, so stdlib already works on Linux/macOS. Gate the swap behind
sys.platform == 'win32': Windows uses CLH, POSIX keeps stdlib RotatingFileHandler.

- hermes_logging.py: platform-conditional import.
- tests/test_hermes_logging.py: import RotatingFileHandler from hermes_logging
  (single source of truth) so the autouse fixture's isinstance checks match the
  real handler class on both platforms.
- pyproject.toml/uv.lock: mark the dep 'sys_platform == "win32"' so portalocker
  /pywin32 only ship where used.

---------

Co-authored-by: tuancookiez-hub <tuancookiez@gmail.com>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…lvage NousResearch#44921) (NousResearch#46794)

* fix(logging): alias RotatingFileHandler to concurrent-log-handler

On Windows, stdlib RotatingFileHandler.doRollover() uses os.rename(), which
fails with PermissionError [WinError 32] whenever another process holds an
append-mode handle on agent.log — essentially always in Hermes (TUI, gateway,
hy_memory server, MCP servers, and on-demand CLI commands all log from separate
processes). This pinned agent.log at the 5 MiB threshold and spammed stderr
with a traceback on every emit (NousResearch#44873).

Add concurrent-log-handler==0.9.29 as a core dep and alias its
ConcurrentRotatingFileHandler as RotatingFileHandler in hermes_logging.py. It
wraps the rename in a cross-process file lock (via portalocker: pywin32 on
Windows, fcntl on POSIX) so only one process rotates at a time. Aliasing keeps
every existing isinstance/class-declaration reference working unchanged.

Co-authored-by: tuancookiez-hub <tuancookiez@gmail.com>

* fix(logging): gate concurrent-log-handler swap to Windows only

The initial salvage aliased RotatingFileHandler -> ConcurrentRotatingFileHandler
unconditionally, which regressed POSIX: CLH opens lazily and rotates via its own
lock path, breaking managed-mode (NixOS) group-writable perms and eager file
creation that _ManagedRotatingFileHandler depends on. CI caught it as 2 failures
in test_managed_mode_*_group_writable on Linux.

The WinError 32 bug (NousResearch#44873) is Windows-specific — POSIX renames an open file
fine, so stdlib already works on Linux/macOS. Gate the swap behind
sys.platform == 'win32': Windows uses CLH, POSIX keeps stdlib RotatingFileHandler.

- hermes_logging.py: platform-conditional import.
- tests/test_hermes_logging.py: import RotatingFileHandler from hermes_logging
  (single source of truth) so the autouse fixture's isinstance checks match the
  real handler class on both platforms.
- pyproject.toml/uv.lock: mark the dep 'sys_platform == "win32"' so portalocker
  /pywin32 only ship where used.

---------

Co-authored-by: tuancookiez-hub <tuancookiez@gmail.com>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…lvage NousResearch#44921) (NousResearch#46794)

* fix(logging): alias RotatingFileHandler to concurrent-log-handler

On Windows, stdlib RotatingFileHandler.doRollover() uses os.rename(), which
fails with PermissionError [WinError 32] whenever another process holds an
append-mode handle on agent.log — essentially always in Hermes (TUI, gateway,
hy_memory server, MCP servers, and on-demand CLI commands all log from separate
processes). This pinned agent.log at the 5 MiB threshold and spammed stderr
with a traceback on every emit (NousResearch#44873).

Add concurrent-log-handler==0.9.29 as a core dep and alias its
ConcurrentRotatingFileHandler as RotatingFileHandler in hermes_logging.py. It
wraps the rename in a cross-process file lock (via portalocker: pywin32 on
Windows, fcntl on POSIX) so only one process rotates at a time. Aliasing keeps
every existing isinstance/class-declaration reference working unchanged.

Co-authored-by: tuancookiez-hub <tuancookiez@gmail.com>

* fix(logging): gate concurrent-log-handler swap to Windows only

The initial salvage aliased RotatingFileHandler -> ConcurrentRotatingFileHandler
unconditionally, which regressed POSIX: CLH opens lazily and rotates via its own
lock path, breaking managed-mode (NixOS) group-writable perms and eager file
creation that _ManagedRotatingFileHandler depends on. CI caught it as 2 failures
in test_managed_mode_*_group_writable on Linux.

The WinError 32 bug (NousResearch#44873) is Windows-specific — POSIX renames an open file
fine, so stdlib already works on Linux/macOS. Gate the swap behind
sys.platform == 'win32': Windows uses CLH, POSIX keeps stdlib RotatingFileHandler.

- hermes_logging.py: platform-conditional import.
- tests/test_hermes_logging.py: import RotatingFileHandler from hermes_logging
  (single source of truth) so the autouse fixture's isinstance checks match the
  real handler class on both platforms.
- pyproject.toml/uv.lock: mark the dep 'sys_platform == "win32"' so portalocker
  /pywin32 only ship where used.

---------

Co-authored-by: tuancookiez-hub <tuancookiez@gmail.com>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…lvage NousResearch#44921) (NousResearch#46794)

* fix(logging): alias RotatingFileHandler to concurrent-log-handler

On Windows, stdlib RotatingFileHandler.doRollover() uses os.rename(), which
fails with PermissionError [WinError 32] whenever another process holds an
append-mode handle on agent.log — essentially always in Hermes (TUI, gateway,
hy_memory server, MCP servers, and on-demand CLI commands all log from separate
processes). This pinned agent.log at the 5 MiB threshold and spammed stderr
with a traceback on every emit (NousResearch#44873).

Add concurrent-log-handler==0.9.29 as a core dep and alias its
ConcurrentRotatingFileHandler as RotatingFileHandler in hermes_logging.py. It
wraps the rename in a cross-process file lock (via portalocker: pywin32 on
Windows, fcntl on POSIX) so only one process rotates at a time. Aliasing keeps
every existing isinstance/class-declaration reference working unchanged.

Co-authored-by: tuancookiez-hub <tuancookiez@gmail.com>

* fix(logging): gate concurrent-log-handler swap to Windows only

The initial salvage aliased RotatingFileHandler -> ConcurrentRotatingFileHandler
unconditionally, which regressed POSIX: CLH opens lazily and rotates via its own
lock path, breaking managed-mode (NixOS) group-writable perms and eager file
creation that _ManagedRotatingFileHandler depends on. CI caught it as 2 failures
in test_managed_mode_*_group_writable on Linux.

The WinError 32 bug (NousResearch#44873) is Windows-specific — POSIX renames an open file
fine, so stdlib already works on Linux/macOS. Gate the swap behind
sys.platform == 'win32': Windows uses CLH, POSIX keeps stdlib RotatingFileHandler.

- hermes_logging.py: platform-conditional import.
- tests/test_hermes_logging.py: import RotatingFileHandler from hermes_logging
  (single source of truth) so the autouse fixture's isinstance checks match the
  real handler class on both platforms.
- pyproject.toml/uv.lock: mark the dep 'sys_platform == "win32"' so portalocker
  /pywin32 only ship where used.

---------

Co-authored-by: tuancookiez-hub <tuancookiez@gmail.com>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: RotatingFileHandler.doRollover() raises PermissionError [WinError 32] on Windows — uncaught, spams stderr on every emit

5 participants