From 99ce6a9fc25a98c2f6c04886ee2f4100565ebc1a Mon Sep 17 00:00:00 2001 From: Kayos Date: Wed, 20 May 2026 08:37:46 -0700 Subject: [PATCH] runner: stage attached files into per-run cwd, drop broken --files arg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old code passed `--files ` to `claude -p`, but the CLI has no such flag — it errored out with "unknown option '--files'" on the first /run call carrying a file. The flag had been there since the file-attachment path was added and was apparently never exercised end to end against current claude versions. New behavior: each file_path is hardlinked (cross-fs falls back to copy) into the per-run cwd under a stable `attachment-N.` name, and the prompt is appended with a `Attached files in the current directory:` manifest pointing at the relative paths. claude -p's Read tool then loads each file on demand — including image content for vision models. acpx /sessions path unaffected — it has its own _format_prompt_with_files. --- clawdforge/runner.py | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/clawdforge/runner.py b/clawdforge/runner.py index 80e4216..35ca569 100644 --- a/clawdforge/runner.py +++ b/clawdforge/runner.py @@ -63,9 +63,6 @@ class Runner: cmd += ["--append-system-prompt", system] if effort: cmd += ["--effort", effort] - if files: - for f in files: - cmd += ["--files", f] timeout = timeout_secs or self.default_timeout @@ -74,6 +71,41 @@ class Runner: cwd = Path(self.runs_dir) / run_id cwd.mkdir(parents=True, exist_ok=True) + # File attachments are surfaced to claude by hardlinking (or + # copying, cross-fs) them into the per-run cwd under stable + # names `attachment-N.`, then appending a short manifest to + # the prompt that references them by relative path. claude -p's + # built-in Read tool then loads each on demand — including image + # content for vision models. + # + # Earlier impl tried `--files` / `--file` flags on the CLI but + # both are claude.ai-hosted file_id forms, not local-path + # accepting; pure-CWD staging is the only sane local path. + attachment_lines: list[str] = [] + if files: + for idx, src in enumerate(files, start=1): + src_p = Path(src) + ext = src_p.suffix.lstrip(".") or "bin" + staged_name = f"attachment-{idx}.{ext}" + staged_path = cwd / staged_name + try: + os.link(src, staged_path) + except OSError: + # Cross-filesystem hardlinks fail with EXDEV; fall back to copy. + shutil.copy2(src, staged_path) + attachment_lines.append(f"- ./{staged_name}") + if attachment_lines: + manifest = ( + "\n\nAttached files in the current directory (use the Read tool to view):\n" + + "\n".join(attachment_lines) + ) + if use_stdin: + # Mutate the input that goes to stdin. + prompt = prompt + manifest + else: + # Mutate the inline-positional arg we already pushed. + cmd[2] = cmd[2] + manifest + started = time.monotonic() try: proc = subprocess.run(