Summary
langstage-cli init scaffolds two files — my_agent.py and langstage.toml. But its overwrite guard is global, not per-file: if either target already exists (and --force isn't passed) it refuses to write both, so the missing file is never created. A real user who loses just one of the two (a stray git clean, an accidental rm, an edit gone wrong) can't run init to regenerate the missing half — it just errors and writes nothing, leaving a half-scaffolded, non-runnable project.
The docstring/README frame the guard as protecting existing files ("refuses to overwrite an existing my_agent.py / langstage.toml unless --force"), but the implementation over-refuses: it also declines to create a file that doesn't exist. And the error names only the existing file, so the user is never told the file they actually needed wasn't written either.
Severity: minor (a recovery-path UX gap; deterministic).
Environment
langstage-cli 0.6.30 (from PyPI, clean venv)
langstage-core 1.0.34, Python 3.11, Linux
Minimal repro
mkdir proj && cd proj
langstage-cli init # writes my_agent.py + langstage.toml (✓)
rm my_agent.py # lose ONLY the agent file
langstage-cli init # try to regenerate the missing my_agent.py
# -> Error: refusing to overwrite existing langstage.toml.
# (writes NOTHING — my_agent.py is still missing)
langstage-cli "hello" # project is now broken
# -> Error: FileNotFoundError: Agent file not found: .../proj/my_agent.py
The reverse is symmetric — keep my_agent.py, delete langstage.toml, run init: Error: refusing to overwrite existing my_agent.py. and the missing langstage.toml is not written.
Evidence (literal output, ANSI stripped)
$ langstage-cli init # in a dir that has langstage.toml but NOT my_agent.py
Error: refusing to overwrite existing langstage.toml.
Re-run with --force to overwrite.
$ ls
langstage.toml # my_agent.py NOT created
$ langstage-cli "hello"
Error: FileNotFoundError: Agent file not found: .../proj/my_agent.py
Expected vs actual
- Expected:
init writes the file(s) that don't exist (here, my_agent.py), and only refuses to overwrite the file(s) that do exist — matching the documented wording. The project is runnable again after re-running init.
- Actual: because one file exists,
init writes neither — the needed, missing file is never created — and the error mentions only the existing file, so the broken state is silent.
Why --force isn't an adequate workaround
--force writes both files unconditionally, so recovering the missing my_agent.py also overwrites the surviving langstage.toml back to the stock default (scaffold_init does an unconditional toml_path.write_text(_INIT_TOML)). A user who customized langstage.toml (a different [agent] spec, [ui] verbose, a pinned [configurable] thread_id) loses those edits. So today there is no way to regenerate just the missing default file without clobbering the customized surviving one.
Root cause
scaffold_init (langstage_cli/cli.py, ~L2010) computes the existing set and refuses globally, then writes both unconditionally:
if not force:
existing = [p.name for p in (agent_path, toml_path) if p.exists()]
if existing:
_status(f"... refusing to overwrite existing {', '.join(existing)}.")
return 1
...
agent_path.write_text(_INIT_AGENT_PY, encoding="utf-8") # always both
toml_path.write_text(_INIT_TOML, encoding="utf-8")
The guard keys off "any file exists", not "this file exists", and the two writes are unconditional after it.
Suggested fix
Make it per-file: write each of my_agent.py / langstage.toml only if it's missing (or --force); skip the ones that already exist, reporting exactly what was written vs skipped. E.g.:
$ langstage-cli init # langstage.toml exists, my_agent.py missing
✓ Wrote my_agent.py
• Skipped langstage.toml (already exists — use --force to overwrite)
That matches the documented "refuses to overwrite an existing …" contract, lets init repair a partially-missing scaffold, and keeps --force as the only way to clobber a file that genuinely exists. (If all-or-nothing is intentional, at minimum the error should state that the missing file was not created, so the user isn't left with a silently broken project.)
Filed by the nightly dogfood routine. Generated by Claude Code
Summary
langstage-cli initscaffolds two files —my_agent.pyandlangstage.toml. But its overwrite guard is global, not per-file: if either target already exists (and--forceisn't passed) it refuses to write both, so the missing file is never created. A real user who loses just one of the two (a straygit clean, an accidentalrm, an edit gone wrong) can't runinitto regenerate the missing half — it just errors and writes nothing, leaving a half-scaffolded, non-runnable project.The docstring/README frame the guard as protecting existing files ("refuses to overwrite an existing
my_agent.py/langstage.tomlunless--force"), but the implementation over-refuses: it also declines to create a file that doesn't exist. And the error names only the existing file, so the user is never told the file they actually needed wasn't written either.Severity: minor (a recovery-path UX gap; deterministic).
Environment
langstage-cli0.6.30 (from PyPI, clean venv)langstage-core1.0.34, Python 3.11, LinuxMinimal repro
The reverse is symmetric — keep
my_agent.py, deletelangstage.toml, runinit:Error: refusing to overwrite existing my_agent.py.and the missinglangstage.tomlis not written.Evidence (literal output, ANSI stripped)
Expected vs actual
initwrites the file(s) that don't exist (here,my_agent.py), and only refuses to overwrite the file(s) that do exist — matching the documented wording. The project is runnable again after re-runninginit.initwrites neither — the needed, missing file is never created — and the error mentions only the existing file, so the broken state is silent.Why
--forceisn't an adequate workaround--forcewrites both files unconditionally, so recovering the missingmy_agent.pyalso overwrites the survivinglangstage.tomlback to the stock default (scaffold_initdoes an unconditionaltoml_path.write_text(_INIT_TOML)). A user who customizedlangstage.toml(a different[agent] spec,[ui] verbose, a pinned[configurable] thread_id) loses those edits. So today there is no way to regenerate just the missing default file without clobbering the customized surviving one.Root cause
scaffold_init(langstage_cli/cli.py, ~L2010) computes the existing set and refuses globally, then writes both unconditionally:The guard keys off "any file exists", not "this file exists", and the two writes are unconditional after it.
Suggested fix
Make it per-file: write each of
my_agent.py/langstage.tomlonly if it's missing (or--force); skip the ones that already exist, reporting exactly what was written vs skipped. E.g.:That matches the documented "refuses to overwrite an existing …" contract, lets
initrepair a partially-missing scaffold, and keeps--forceas the only way to clobber a file that genuinely exists. (If all-or-nothing is intentional, at minimum the error should state that the missing file was not created, so the user isn't left with a silently broken project.)Filed by the nightly dogfood routine. Generated by Claude Code