Skip to content

feat: --prompt-file flag to read long prompts from disk (v1.2.4)#13

Merged
JonyanDunh merged 1 commit intomainfrom
feat/prompt-file-v1.2.4
Apr 11, 2026
Merged

feat: --prompt-file flag to read long prompts from disk (v1.2.4)#13
JonyanDunh merged 1 commit intomainfrom
feat/prompt-file-v1.2.4

Conversation

@JonyanDunh
Copy link
Copy Markdown
Owner

Add a new --prompt-file <path> argument to /watchdog:start so users can pass multi-paragraph prompts (Markdown task specs with headings, code fences, quoted strings, etc.) without mangling them through shell-argument escaping.

Problem

Slash command ! shell blocks invoke setup-watchdog.js via $ARGUMENTS literal substitution. Any unescaped ", backtick, $, or newline in the user's inline prompt breaks bash parsing with unexpected EOF. In practice this hits every realistic multi-paragraph Markdown prompt the moment a user pastes it into /watchdog:start "...".

/bin/bash: eval: line 1: unexpected EOF while looking for matching `"'

ralph-loop (from which watchdog is derived) has the same failure mode — I confirmed this against the upstream setup-ralph-loop.sh source — and does not provide a file or stdin fallback.

Solution

New --prompt-file <path> flag in scripts/setup-watchdog.js. The file is read directly with Node's fs.readFileSync, bypassing shell argument parsing entirely. Mutually exclusive with an inline <PROMPT> positional — pick one or the other.

/watchdog:start --prompt-file ./tmp/my-task.md --max-iterations 20

Path handling

Delegated to path.resolve(), which is platform-aware:

  • Linux / macOS / WSL — POSIX absolute (/home/you/...) and relative (./tmp/..., ../notes.txt) paths
  • Windows (native cmd.exe / PowerShell)C:\Users\you\..., C:/Users/you/..., and UNC \\server\share\... paths all work through Node's fs APIs
  • Relative paths resolve against process.cwd() on every platform

BOM / CRLF / encoding

  • Leading UTF-8 BOM is stripped automatically. Windows Notepad and PowerShell's default Set-Content silently prepend U+FEFF to UTF-8 files, and .trim() does not remove it (BOM is not whitespace), so without this Claude would see an invisible zero-width marker as the first character of the prompt
  • CRLF line endings inside the content are preserved byte-for-byte — we do not rewrite to LF
  • ~ is NOT expanded by watchdog — that is the shell's job. bash/zsh already expand it before the args reach the script; cmd.exe users should pass an absolute path or %USERPROFILE%\...

Error paths

All return exit 1 with a clear stderr message:

Case Message
ENOENT prompt file not found: <resolved>
EISDIR --prompt-file expects a file, got a directory: <resolved>
EACCES/EPERM permission denied reading prompt file: <resolved>
empty file (after trim) prompt file is empty: <resolved>
missing path argument --prompt-file requires a path argument
combined with inline prompt --prompt-file cannot be combined with a positional prompt

Watchdog vs ralph-loop — new comparison row

Added a Prompt input row to the comparison table in all 7 READMEs, reflecting that watchdog now has a clean escape-hatch that ralph-loop lacks:

Watchdog ralph-loop
Prompt input Inline via $ARGUMENTS, or --prompt-file <path> — reads the file directly with Node's fs.readFileSync, bypassing shell argument parsing entirely. Safe for multi-paragraph Markdown containing newlines, quotes, backticks, $, etc. UTF-8 BOM is stripped automatically; CRLF is preserved byte-for-byte. Inline via $ARGUMENTS in the slash command's ! shell block only. Any unescaped ", backtick, $, or newline in the prompt breaks bash parsing with unexpected EOF. No file or stdin fallback.

Verified against the upstream source at https://github.com/anthropics/claude-plugins-official/tree/main/plugins/ralph-loopsetup-ralph-loop.sh only parses --max-iterations, --completion-promise, -h/--help, and positional args.

Scope

  • scripts/setup-watchdog.jsparseArgs() recognizes --prompt-file, new readPromptFile() helper centralizes file read + BOM strip + error mapping, main() enforces mutual exclusion
  • commands/start.mdargument-hint updated to show the alternate form
  • commands/help.md — new --prompt-file option with the full path-handling reference
  • test/setup.test.js+17 tests: happy path, shell metacharacters in content, whitespace trimming, UTF-8 BOM strip, relative path, ./name relative path, CRLF content, non-ASCII filename, symlink following (non-Windows), permission denied (non-Windows non-root), missing file, directory target, empty file, missing path arg, mutual exclusion with inline, help text listing new form
  • README.{md,zh,es,ja,ko,vi,pt}.md — new "Long prompts from a file" subsection under Commands, plus the new "Prompt input" row in the Watchdog vs ralph-loop comparison table
  • .claude-plugin/plugin.json — version → 1.2.4
  • .claude-plugin/marketplace.json — version → 1.2.4 (both the plugin entry and the marketplace top-level)

Zero changes to lib/ or hooks/. Purely additive: no existing behavior of /watchdog:start is modified — users who already pass inline prompts see no change.

Test plan

  • 96-test suite passes locally (94 active + 2 skipped-inside-Claude-Code, 0 failures)
  • setup.test.js alone: 27 tests — 26 active + 1 pre-existing skip
  • Root cause debugged by reproducing the real bash unexpected EOF on a pasted multi-paragraph Markdown prompt
  • CI will confirm on the standard matrix (ubuntu / macos / windows × Node 18 / 20 / 22)

Install / upgrade

/plugin marketplace update claude-code-watchdog
/reload-plugins

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

Add a new `--prompt-file <path>` argument to `/watchdog:start` so users
can pass multi-paragraph prompts (Markdown task specs with headings,
code fences, quoted strings, etc.) without mangling them through shell-
argument escaping.

## Problem

Slash command `!` shell blocks invoke setup-watchdog.js via `$ARGUMENTS`
literal substitution. Any unescaped `"`, backtick, `$`, or newline in
the user's inline prompt breaks bash parsing with `unexpected EOF`. In
practice this hits every realistic multi-paragraph Markdown prompt the
moment a user pastes it into `/watchdog:start "..."`.

ralph-loop (from which watchdog is derived) has the same failure mode
and does not provide a file or stdin fallback.

## Solution

New `--prompt-file <path>` flag in scripts/setup-watchdog.js. The file
is read directly with Node's `fs.readFileSync`, bypassing shell
argument parsing entirely. Mutually exclusive with an inline `<PROMPT>`
positional — pick one or the other.

Path handling is delegated to `path.resolve()`, which is platform-
aware:
  - Linux/macOS/WSL POSIX paths (absolute + relative, `./name`, `../name`)
  - Windows `C:\...`, `C:/...`, and UNC `\\server\share\...` paths
  - Relative paths resolve against `process.cwd()` on every platform

Leading UTF-8 BOM is stripped automatically — Windows Notepad and
PowerShell's default `Set-Content` silently prepend U+FEFF to UTF-8
files, and `.trim()` does not remove it (BOM is not whitespace), so
without this Claude would see an invisible zero-width marker as the
first character of the prompt.

CRLF line endings inside the content are preserved byte-for-byte. `~`
is NOT expanded by watchdog — that is the shell's job, and bash/zsh
already expand it before the args reach the script. `cmd.exe` users
should pass an absolute path or `%USERPROFILE%\...`.

Error paths return clean messages:
  - ENOENT => "prompt file not found: <resolved>"
  - EISDIR => "--prompt-file expects a file, got a directory: <resolved>"
  - EACCES/EPERM => "permission denied reading prompt file: <resolved>"
  - empty file => "prompt file is empty: <resolved>"
  - missing arg => "--prompt-file requires a path argument"
  - combined with inline => "--prompt-file cannot be combined with a
    positional prompt"

## What changed

- scripts/setup-watchdog.js — parseArgs() recognizes --prompt-file,
  new readPromptFile() helper centralizes file read + BOM strip +
  error mapping, main() enforces mutual exclusion
- commands/start.md — argument-hint updated to show the alternate
  form
- commands/help.md — new --prompt-file option with the full path-
  handling reference (POSIX / Windows / UNC / BOM / CRLF / spaces /
  encoding / ~ expansion)
- test/setup.test.js — +17 tests: happy path, shell metacharacters
  in content, whitespace trimming, UTF-8 BOM strip, relative path,
  ./name relative path, CRLF content, non-ASCII filename, symlink
  following (non-Windows), permission denied (non-Windows non-root),
  missing file, directory target, empty file, missing path arg,
  mutual exclusion with inline, help text listing new form
- README.{md,zh,es,ja,ko,vi,pt}.md — new "Long prompts from a file"
  subsection under Commands, plus a new "Prompt input" row in the
  Watchdog vs ralph-loop comparison table
- .claude-plugin/plugin.json — version => 1.2.4
- .claude-plugin/marketplace.json — version => 1.2.4 (both the
  plugin entry and the marketplace top-level)

## Test plan

- [x] 96-test suite passes locally (94 active + 2 skipped-inside-
      Claude-Code, 0 failures)
- [x] setup.test.js: 27 tests — 26 active + 1 pre-existing skip
- [x] Root cause debugged by reproducing the real bash `unexpected
      EOF` on a pasted multi-paragraph Markdown prompt
- [ ] CI will confirm on the standard matrix (ubuntu/macos/windows
      × Node 18/20/22)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JonyanDunh JonyanDunh merged commit 93bb63c into main Apr 11, 2026
11 checks passed
@JonyanDunh JonyanDunh deleted the feat/prompt-file-v1.2.4 branch April 11, 2026 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant