mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-14 14:12:44 +00:00
A cron job with a per-job `workdir` overrides the process-global `os.environ["TERMINAL_CWD"]` for the entire duration of its agent run and restores it afterwards. The scheduler dispatches workdir jobs on a single-thread sequential pool and workdir-less jobs on a separate parallel pool, and the in-code comments claimed this made the override safe. That only prevents two workdir jobs from overlapping each other. The two pools run concurrently in the same process and share `os.environ`, so while a workdir job has `TERMINAL_CWD` pointed at its project directory, any workdir-less job firing in the same window reads that same global through the terminal, file, and code-exec tools and runs its commands in the wrong directory. The corruption window spans the whole workdir-job run, and a file write or delete can land in another job's tree. This serializes the override with a writer-preferring readers-writer lock. Workdir jobs acquire it as writers (exclusive for their whole run); workdir- less jobs acquire it as readers, so they still run in parallel with each other but never alongside a workdir job's override. The guarantee is based on run overlap rather than tick boundaries, so it also holds when a workdir job spans ticks. ## What does this PR do? Fixes a directory-isolation bug in the cron scheduler: a workdir cron job's process-global `TERMINAL_CWD` override could be observed by a concurrently running workdir-less cron job, causing that job's shell/file/code-exec commands to execute in the wrong directory. ## Related Issue N/A ## Type of Change - [x] 🐛 Bug fix (non-breaking change that fixes an issue) - [ ] ✨ New feature (non-breaking change that adds functionality) - [ ] 🔒 Security fix - [ ] 📝 Documentation update - [ ] ✅ Tests (adding or improving test coverage) - [ ] ♻️ Refactor (no behavior change) - [ ] 🎯 New skill (bundled or hub) ## Changes Made - `cron/scheduler.py`: add `_ReadWriteLock` (writer-preferring) and the module-global `_terminal_cwd_lock`. - `cron/scheduler.py`: in `run_job`, acquire the lock as a writer for workdir jobs and as a reader for workdir-less jobs, spanning the `TERMINAL_CWD` override and its restore in the `finally` block. - `cron/scheduler.py`: correct the stale comments in `run_job` and `tick` that claimed the sequential pool alone made the override safe. - `tests/cron/test_terminal_cwd_lock.py`: new tests for reader concurrency, writer exclusion, and the no-cross-observation regression. ## How to Test 1. `python -m pytest tests/cron/test_terminal_cwd_lock.py -q` — the regression test `test_reader_never_observes_writer_override` fails without the lock and passes with it. 2. `python -m pytest tests/cron/test_cron_workdir.py tests/cron/test_parallel_pool.py -q` — confirms the existing `TERMINAL_CWD` set/restore and pool behaviour are unchanged. ## Checklist ### Code - [x] I've read the Contributing Guide - [x] My commit messages follow Conventional Commits (`fix(scope):`, etc.) - [x] I searched for existing PRs to make sure this isn't a duplicate - [x] My PR contains only changes related to this fix - [x] I've run the affected `tests/cron/` suites and all tests pass - [x] I've added tests for my changes (required for bug fixes) - [x] I've tested on my platform: macOS 15 (Darwin 25.5) ### Documentation & Housekeeping - [x] I've updated relevant documentation (docstrings/comments) — or N/A - [x] I've updated `cli-config.yaml.example` if I added/changed config keys — N/A - [x] I've updated `CONTRIBUTING.md` or `AGENTS.md` if I changed architecture — N/A - [x] I've considered cross-platform impact (Windows, macOS) — uses stdlib `threading` only - [x] I've updated tool descriptions/schemas if I changed tool behavior — N/A |
||
|---|---|---|
| .. | ||
| scripts | ||
| __init__.py | ||
| blueprint_catalog.py | ||
| jobs.py | ||
| lifecycle_guard.py | ||
| scheduler.py | ||
| scheduler_provider.py | ||
| suggestion_catalog.py | ||
| suggestions.py | ||