Bug
LocalREPL._temp_cwd() (rlm/environments/local_repl.py:505) changes the process-wide working directory around every code execution:
def _temp_cwd(self):
old_cwd = os.getcwd()
try:
os.chdir(self.temp_dir)
yield
finally:
os.chdir(old_cwd)
os.getcwd()/os.chdir() are global to the process, so running multiple RLM instances concurrently in threads (e.g. evaluating N samples in a ThreadPoolExecutor, each with its own RLM(environment="local")) races:
- Thread A captures
old_cwd while the process is sitting in thread B's temp_dir; B finishes and deletes its temp dir; A's finally (or its next _temp_cwd() entry) chdirs into a deleted directory:
FileNotFoundError: [Errno 2] No such file or directory: '/var/folders/.../repl_env_<uuid>_...'
File "rlm/environments/local_repl.py", line 554, in execute_code
with self._capture_output() as (stdout_buf, stderr_buf), self._temp_cwd():
- Even without the crash, user code that touches relative paths can silently run in another instance's cwd. (
_capture_output() on the same line redirects process-wide stdout/stderr and has the same cross-instance contamination concern.)
Repro sketch
from concurrent.futures import ThreadPoolExecutor
from rlm import RLM
def run(i):
r = RLM(backend="openai", environment="local", max_iterations=4)
return r.completion(f"task {i}", root_prompt="count the lines")
with ThreadPoolExecutor(max_workers=4) as pool:
list(pool.map(run, range(16))) # intermittent FileNotFoundError at local_repl.py:554
At 4 threads we hit the FileNotFoundError on roughly half the rollouts in some workloads (rlms==0.1.3, repo @ caf0bff, macOS).
Note this is distinct from PR #136, which parallelizes sub-calls within one RLM instance — this race is across instances sharing a process.
Suggested directions
- Avoid
chdir entirely: resolve file operations against self.temp_dir (or run user code with cwd= via a subprocess-backed executor), or
- guard
execute_code with a module-level lock (correct but serializes all local REPLs), and/or
- document that
LocalREPL is single-instance-per-process and recommend ipython subprocess mode / docker for concurrent use.
Happy to provide more detail — found while reproducing the "LM harnesses are compositional generalizers" results; we worked around it by serializing all RLM rollouts.
Bug
LocalREPL._temp_cwd()(rlm/environments/local_repl.py:505) changes the process-wide working directory around every code execution:os.getcwd()/os.chdir()are global to the process, so running multipleRLMinstances concurrently in threads (e.g. evaluating N samples in aThreadPoolExecutor, each with its ownRLM(environment="local")) races:old_cwdwhile the process is sitting in thread B'stemp_dir; B finishes and deletes its temp dir; A'sfinally(or its next_temp_cwd()entry)chdirs into a deleted directory:_capture_output()on the same line redirects process-wide stdout/stderr and has the same cross-instance contamination concern.)Repro sketch
At 4 threads we hit the FileNotFoundError on roughly half the rollouts in some workloads (rlms==0.1.3, repo @ caf0bff, macOS).
Note this is distinct from PR #136, which parallelizes sub-calls within one RLM instance — this race is across instances sharing a process.
Suggested directions
chdirentirely: resolve file operations againstself.temp_dir(or run user code withcwd=via a subprocess-backed executor), orexecute_codewith a module-level lock (correct but serializes all local REPLs), and/orLocalREPLis single-instance-per-process and recommendipythonsubprocess mode /dockerfor concurrent use.Happy to provide more detail — found while reproducing the "LM harnesses are compositional generalizers" results; we worked around it by serializing all RLM rollouts.