fix: Python 3.14 compatibility (daemon_pool worker signature, gateway liveness argv, dotenv reload race) - #61224
fix: Python 3.14 compatibility (daemon_pool worker signature, gateway liveness argv, dotenv reload race)#61224slow4cyl wants to merge 4 commits into
Conversation
Related, not a duplicate. The |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused compatibility work. The daemon-pool branch matches CPython 3.14's _create_worker_context() / three-argument _worker shape, and current main still has the pre-3.14 call in tools/daemon_pool.py:52-60.
Problems
- The PR cannot yet make Hermes runnable as a supported Python 3.14 installation:
pyproject.toml:13-20deliberately capsrequires-pythonbelow 3.14 because required Rust-backed dependencies lack cp314 wheels. hermes_cli/env_loader.py:162invokes the Latin-1 fallback outside the newKeyErrorretry handler. A transient KeyError in that fallback escapes rather than retrying.- No regression tests are included for the three changed paths; relevant suites already exist at
tests/tools/test_daemon_pool.py,tests/gateway/test_status.py, andtests/hermes_cli/test_env_loader.py.
Suggested changes
- Put both encoding paths inside the KeyError retry loop, then add focused regressions for the 3.14 worker signature, bare
hermesgateway argv, and KeyError on both encoding paths. - Re-scope the Python 3.14 claim or separately resolve the packaging constraint in
pyproject.toml:13-20.
Automated hermes-sweeper review.
| # gateway/kanban_watchers.py), so dict.update can KeyError on a key that | ||
| # vanished between keys() and __getitem__ (crashed cron jobs, #external-probe). | ||
| # The window is microseconds and the failing load applied nothing (the error | ||
| # is in .dict() before set_as_environment_variables), so retrying is safe. |
There was a problem hiding this comment.
This Latin-1 fallback is outside the new except KeyError block. If the same concurrent-environment KeyError occurs after UTF-8 decoding fails, it escapes without a retry; wrap the encoding fallback inside the retry attempt.
…mpat Python 3.14 changed ThreadPoolExecutor._worker to (executor_ref, ctx, work_queue), folding initializer/initargs into a worker context object. The subclass still passed the old 4-tuple, so every pool thread died at spawn: on 3.14 the kanban dispatcher spawned zero workers. Branch on the presence of _create_worker_context so 3.8-3.13 keep the old path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
main.py calls setproctitle("hermes"), erasing the gateway subcommand
from /proc cmdline. The liveness check then failed its "looks like a
gateway" test and reported a healthy systemd gateway as OFFLINE. When
argv carries no gateway subcommand but PID + start_time match the
persisted record, defer to the recorded gateway identity.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
python-dotenv's env.update(os.environ) can race another thread mutating os.environ (the in-gateway kanban decomposer pins/unpins env vars per board), raising a transient KeyError that crashed in-gateway cron jobs. Retry once on that specific failure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ack, regression tests
Per review (scope: forward-compat for Python 3.14, NOT a support claim —
pyproject requires-python stays capped below 3.14 until the Rust-backed
deps ship cp314 wheels; this branch never touched it):
- env_loader: hoist the latin-1 fallback into the KeyError retry loop —
a transient KeyError raised inside the fallback previously escaped
instead of retrying (review finding at env_loader.py:162). Both
encoding paths now run inside the 3-attempt loop.
- gateway/status: narrow the setproctitle fallback. Deferring to the
record whenever the argv lacked a gateway subcommand regressed
test_runtime_status_running_pid_rejects_stale_record_for_supervisor_pid
(a readable s6-supervise cmdline on a matching-start-time PID was
reported running). New _cmdline_is_bare_hermes_proctitle() accepts
exactly the stripped setproctitle("hermes") shape — one token,
basename hermes/hermes.exe; every other readable cmdline keeps the
strict live-argv judgment.
- daemon_pool: extract _worker_args() so the version-guarded branch is
directly unit-testable; behavior unchanged (hasattr probe on the
instance — on 3.14 _create_worker_context is set in __init__).
- Tests (all three suites named by the review):
* daemon_pool: probe-matches-interpreter guard, both _worker arg
shapes pinned via stand-in executors (covers the 3.14 branch on
<=3.13 CI and the legacy branch on 3.14), live-pool spawn guard.
* status: bare-hermes proctitle reported running; stripped-proctitle
fallback still validates the record; non-proctitle readable
cmdlines stay strict; direct proctitle matcher tests.
* env_loader: KeyError retry on the utf-8 path, on the latin-1
fallback (the review's exact case), persistent-KeyError re-raise
after 3 attempts, and a real non-UTF-8 file loading via latin-1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bf80f83 to
743d364
Compare
|
Re-scoped as requested: this is forward-compatibility for 3.14 — every 3.14 branch is feature-detected and a no-op on ≤3.13, and |
Three small fixes that together make the agent run correctly on Python 3.14. Each is its own commit:
fix(daemon_pool): Python 3.14 ThreadPoolExecutor._worker signature compat
Python 3.14 changed ThreadPoolExecutor._worker to (executor_ref, ctx,
work_queue), folding initializer/initargs into a worker context object.
The subclass still passed the old 4-tuple, so every pool thread died at
spawn: on 3.14 the kanban dispatcher spawned zero workers. Branch on the
presence of _create_worker_context so 3.8-3.13 keep the old path.
fix(gateway): status liveness check tolerates setproctitle-stripped argv
main.py calls setproctitle("hermes"), erasing the gateway subcommand
from /proc cmdline. The liveness check then failed its "looks like a
gateway" test and reported a healthy systemd gateway as OFFLINE. When
argv carries no gateway subcommand but PID + start_time match the
persisted record, defer to the recorded gateway identity.
fix(env_loader): retry dotenv reload on transient KeyError
python-dotenv's env.update(os.environ) can race another thread mutating
os.environ (the in-gateway kanban decomposer pins/unpins env vars per
board), raising a transient KeyError that crashed in-gateway cron jobs.
Retry once on that specific failure.
🤖 Generated with Claude Code