Commit 7e5cdfa
fix(providers): atomic .env write via tempfile + os.replace (nesquena#1164 cross-process)
The contributor PR's within-process fix (_ENV_LOCK on the load→modify→write
cycle) is correct, but doesn't close the cross-process race that is the
actual root cause of nesquena#1164. The hermes-agent CLI/Telegram bot uses
hermes_cli.config.save_env_value, which writes .env atomically via
tempfile + os.replace. The WebUI's _write_env_file was using
os.open(..., O_TRUNC) — between the truncate and the complete write,
a concurrent reader sees a partial or empty file. The agent has a
_sanitize_env_lines workaround for the resulting corruption pattern
(concatenated KEY=VALUE pairs from interleaved writes / partial reads),
which is a strong signal that this race actually occurs in practice.
Fix: stage the write through tempfile.mkstemp() in the same directory
(same filesystem, so os.replace is atomic on POSIX), fsync before
rename, chmod 0600 before rename so readers never see a 0644 window,
and clean up the tempfile on any exception. Matches the exact pattern
hermes_cli/config.py:save_env_value uses.
Within-process safety from the PR's _ENV_LOCK is preserved (the whole
sequence still runs inside the with-lock block).
Also added a regression test asserting the new invariants:
- tempfile staging present
- os.replace called
- O_TRUNC pattern is gone
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>1 parent d4de119 commit 7e5cdfa
2 files changed
Lines changed: 38 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
153 | 153 | | |
154 | 154 | | |
155 | 155 | | |
156 | | - | |
157 | | - | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
158 | 161 | | |
159 | | - | |
160 | | - | |
161 | | - | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
162 | 179 | | |
163 | 180 | | |
164 | 181 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
152 | 152 | | |
153 | 153 | | |
154 | 154 | | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
0 commit comments