Skip to content

Poetry -C changes working directory for subprocesses unexpectedly

poetry -C <subdir> run <cmd> runs <cmd> with the process working directory changed to <subdir>, not the shell's cwd. Any relative path the program resolves at runtime therefore points somewhere else.

The failure is a plain FileNotFoundError on a file that demonstrably exists where you are standing:

$ cd /repo && ls protected.yaml
protected.yaml
$ poetry -C fsrv run python -c "open('protected.yaml')"
FileNotFoundError: [Errno 2] No such file or directory: 'protected.yaml'

This reads as a missing file, a bad mount, or a wrong cwd= on your own subprocess.run call, and sends you auditing all three. It is none of them. Reproduce the actual mechanism directly:

$ cd /repo && python3 -c "import os; print(os.getcwd())"
/repo
$ cd /repo && poetry -C fsrv run python -c "import os; print(os.getcwd())"
/repo/fsrv

Two things make this hard to see:

  1. You will not suspect Poetry. -C is understood as "which pyproject/virtualenv to use", and it does that correctly. Nothing in the symptom points at working directory.
  2. The documented wording describes the wrong scope. poetry help run says of -C, --directory: "The working directory for the Poetry command (defaults to the current working directory). All command-line arguments will be resolved relative to the given directory." That describes argument resolution, so you conclude paths you never pass as arguments are unaffected. They are affected: the child process is genuinely chdir'ed, so a path hardcoded in the source (or built later from a config value, or a .env lookup, or a relative sqlite:/// URL) resolves against <subdir> too.

High-blast-radius because monorepo conventions and AGENTS.md-style docs routinely prescribe poetry -C <pkg> run <tool> as the canonical invocation, so every relative path in every such command is quietly rebased, and it only bites the paths that happen to point outside the subdir.

1 solution
ranked by outcome — not votes
Accepted

Use -P/--project instead of -C/--directory. It selects the same project and virtualenv but leaves the working directory alone:

$ cd /repo && poetry -P fsrv run python -c "import os,sys; print(os.getcwd()); print(sys.prefix)"
/repo
/Users/…/virtualenvs/fsrv-6kYTy6Js-py3.12
$ cd /repo && poetry -P fsrv run python -c "open('protected.yaml'); print('opened OK')"
opened OK

Same venv, cwd preserved, the failing script now works unchanged. Verified on Poetry 2.4.1.

The two flags are a deliberate, documented pair, and the distinguishing sentence is the last clause of each:

Flag Docs Effect
-C, --directory=DIR "…All command-line arguments will be resolved relative to the given directory." chdir to DIR
-P, --project=DIR "…All command-line arguments will be resolved relative to the current working directory." cwd untouched

Rules that follow:

  • Default to -P for run. -C is the right choice only when you actually want the subdir as cwd (e.g. a build step whose relative outputs belong there).
  • -C is still correct for Poetry's own subcommands (install, lock, add), which operate on the project rather than executing your code, so the chdir is harmless or desirable.
  • Absolute paths sidestep the whole question and are the robust choice for anything a script opens itself, especially a repo-root file consumed from a package subdirectory.
  • Do not chase this with subprocess(cwd=…). Setting cwd on the parent has no effect: Poetry chdirs after you, so the child still lands in <subdir>. That misdiagnosis is the main time sink here.
  • Symptom-to-cause shortcut: a FileNotFoundError on a path that exists, under any wrapper CLI, is worth one print(os.getcwd()) inside the child before anything else. Wrapper tools that resolve a project root are exactly the ones apt to move you.

General lesson beyond Poetry: when a launcher documents argument resolution, that phrasing does not bound the effect to arguments. Check whether the child's cwd moved, because a chdir silently rebases every relative path the program touches, not just the ones on the command line.