Skip to content

Add relayfile listen — stream events, run a command per match#332

Closed
khaliqgant wants to merge 10 commits into
mainfrom
claude/relayfile-listen
Closed

Add relayfile listen — stream events, run a command per match#332
khaliqgant wants to merge 10 commits into
mainfrom
claude/relayfile-listen

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jun 22, 2026

Copy link
Copy Markdown
Member

What

Adds relayfile listen (alias: watch) to the CLI. Connects to the workspace WebSocket event stream and optionally executes a shell command for each matching event.

# Wake Claude when a new Linear issue is filed
relayfile listen --provider linear --event file.created \
  --run "claude --print 'New issue at {{path}}. Read it and suggest a priority and owner.'"

# Extract action items from Granola meeting notes
relayfile listen --provider granola --event file.created \
  --run "claude --print 'New meeting notes at {{path}}. Extract action items and owners.'"

# Draft follow-ups from Fathom call recordings
relayfile listen --provider fathom --event file.created \
  --run "claude --print 'New call at {{path}}. Write a follow-up with key decisions.'"

Flags

Flag Description
--provider PROVIDER Shorthand path filter — --provider linear maps to /linear/**
--path GLOB Explicit glob filter, e.g. /linear/issues/**
--event TYPE Filter by type: file.created, file.updated, file.deleted
--run CMD Shell command per event. Placeholders: {{path}}, {{type}}, {{provider}}, {{revision}}, {{event}} (full JSON)
--format text|json Output format when --run is not set (default: text)

How it works

  • Opens a WebSocket to /v1/workspaces/{id}/fs/ws with from=now (skips backlog, live events only)
  • Applies server-side path filtering via query param when --provider or --path is set
  • Applies client-side event-type filtering for --event
  • For --run: shells out to sh -c <expanded-cmd> per event; stdout goes to the caller's stdout, stderr to stderr
  • SIGINT/SIGTERM cancel the context and close the connection cleanly

Scope

  • Provider webhooks are already normalized by Agent Relay Cloud before they reach the workspace, so --run receives clean, consistent data regardless of which SaaS tool fired the event
  • relayfile help listen shows runnable examples for Linear, Notion, HubSpot, Asana, Shortcut, Granola, and Fathom, plus a pointer to AgentWorkforce/factory for teams who want the same loop running headlessly at scale

Test plan

  • relayfile listen --help prints usage with examples
  • relayfile listen connects and prints events in text format
  • relayfile listen --format json prints raw JSON per event
  • relayfile listen --provider linear filters to /linear/** events only
  • relayfile listen --event file.created filters by type
  • relayfile listen --run "echo {{path}}" executes once per matching event
  • Ctrl+C exits cleanly with status 0
  • go build ./cmd/relayfile-cli/ passes

🤖 Generated with Claude Code

https://claude.ai/code/session_01PF9GK3mPDyWEKXpaJ8ReB9


Generated by Claude Code

Review in cubic

Streams live file events via WebSocket and optionally executes a shell
command per matching event, making it trivial to wire any provider
webhook to Claude or any other agent.

  relayfile listen --provider linear --event file.created \
    --run "claude --print 'New issue at {{path}}. Suggest priority and owner.'"

Flags:
  --provider   shorthand path filter: --provider linear → /linear/**
  --path       explicit glob filter (e.g. /linear/issues/**)
  --event      type filter: file.created / file.updated / file.deleted
  --run        shell command per event with {{path}}, {{type}},
               {{provider}}, {{revision}}, {{event}} placeholders
  --format     text (default) or json when --run is not set

`watch` is registered as an alias for discoverability.

Help text includes concrete examples for Linear, Notion, HubSpot, Asana,
Shortcut, Granola, and Fathom, and a pointer to AgentWorkforce/factory
for teams who want the same loop running headlessly at scale.

README gains a "React to events locally" section with runnable snippets
for the most common use cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PF9GK3mPDyWEKXpaJ8ReB9
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7b44c587-ef19-472e-8d5c-02474933483e

📥 Commits

Reviewing files that changed from the base of the PR and between bb59431 and eb565f4.

📒 Files selected for processing (2)
  • README.md
  • docs/guides/self-hosted-n8n.md
✅ Files skipped from review due to trivial changes (2)
  • docs/guides/self-hosted-n8n.md
  • README.md

📝 Walkthrough

Walkthrough

Adds three new CLI subcommands: relayfile listen streams workspace filesystem events over WebSocket with per-event filtering and optional shell command execution; relayfile dev performs onboarding checks and delegates to listen; relayfile supervisor manages background listen daemons via systemd (Linux) and launchd (macOS). README is rewritten to focus on the reactive loop model, mount-based coordination, and comparisons to MCP and webhook forwarders. New pricing guide and self-hosted n8n integration guide are added.

Changes

relayfile listen, dev, and supervisor subcommands

Layer / File(s) Summary
Listen WebSocket implementation and filtering
cmd/relayfile-cli/main.go
Introduces listenEvent JSON struct, WebSocket imports (nhooyr.io/websocket), runListen connecting to /v1/workspaces/{id}/fs/ws with auth headers, per-event filtering (--path, --provider, --event), JSON and text output modes, close-status handling, and pong/invalid-payload skipping.
Listen --run template expansion and --background daemonization
cmd/relayfile-cli/main.go
Implements listenExpandTemplate to expand {{path}}, {{type}}, {{provider}}, {{revision}}, {{event}} placeholders in --run shell commands executed via sh -c. Adds --background flag to spawn a detached listen daemon with pid/log file management and internal --daemonized flag for subprocess coordination.
Supervisor daemon management via systemd and launchd
cmd/relayfile-cli/main.go
Implements runSupervisor to install, uninstall, and check status of background listen daemons on Linux (systemd user units with [Service] and [Install] sections) and macOS (launchd plist agents with XML escaping). Includes shelljoin for shell quoting and plistEscapeXML for plist XML entity escaping.
CLI routing and help/usage updates
cmd/relayfile-cli/main.go
Extends the subcommand dispatcher to route listen, watch, dev, and supervisor to their handlers. Updates printHelpForArgs routing, adds listen/dev/supervisor synopsis lines to the main usage banner, and includes them in the Subcommands help listing.

Documentation updates: README, pricing, and integration guides

Layer / File(s) Summary
README reactive loop and read/write model
README.md
Replaces the initial description with a "reactive loop" narrative centered on relayfile listen, including example commands that filter by provider/event/path and run agent CLIs per match. Adds a "Read, write, coordinate" section documenting the mount-based read/write model, real-time multi-agent coordination, and provider alias discovery via relayfile tree.
README MCP comparison and Cloud SDK section
README.md
Adds a focused "vs. MCP and webhook forwarders" section contrasting Relayfile's event+context delivery via pre-populated files. Introduces a "Cloud agents (SDK)" section describing TypeScript SDK mounting flow, ensureMountedWorkspace function, readiness/connection error cases, and verifyProvider.
README quick-start, ecosystem, and docs reorganization
README.md
Reworks "Quick start" with updated hosted and OSS setup instructions. Reorganizes "Ecosystem" to position the repo as the file server/mount layer and summarize adapters/providers. Updates the "Docs" link list to getting-started, cloud integration, pricing, and API reference.
Pricing guide
docs/guides/pricing.md
New pricing documentation with a feature/value table comparing Free/Starter/Team/Enterprise by price, events per month, workspace limits, and features. Defines event counting, Free tier, Enterprise bring-your-own-connections, Starter/Team overage billing, and hosted vs self-hosted charge behavior.
Self-hosted n8n integration guide
docs/guides/self-hosted-n8n.md
New guide documenting the end-to-end n8n + Relayfile stack architecture, startup steps, credential configuration, workflow setup with Relayfile nodes, event-driven listening via relayfile listen, background persistence with relayfile supervisor, reverse triggering, and data residency guarantees.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as relayfile listen
  participant WS as Workspace WebSocket
  participant Shell as sh -c

  User->>CLI: relayfile listen --event write --run "echo {{path}}"
  CLI->>WS: Connect to /v1/workspaces/{id}/fs/ws
  WS-->>CLI: Stream event payloads
  CLI->>CLI: Apply --provider/--path/--event filters
  CLI->>Shell: Execute expanded --run command on match
  Shell-->>CLI: Return exit status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Three commands hop in, a trifecta so fine:
listen streams events along the time line.
With --run and templates, each file's a delight,
While supervisor keeps daemons running right.
The README now glows with the reactive loop's art—
Relayfile's relay finds home in each heart! 🏠✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly summarizes the main feature addition: introducing the relayfile listen command with streaming event capability and per-event command execution.
Description check ✅ Passed The description comprehensively explains the relayfile listen feature, its flags, usage examples, and test plan, all directly related to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/relayfile-listen
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch claude/relayfile-listen

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 markdownlint-cli2 (0.22.1)
docs/guides/self-hosted-n8n.md

markdownlint-cli2 v0.22.1 (markdownlint v0.40.0)
Error: Unable to use configuration file '/coderabbit-1.markdownlint-cli2.jsonc'; ENOENT: no such file or directory, open '/coderabbit-1.markdownlint-cli2.jsonc'
at throwForConfigurationFile (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:48:9)
at readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:169:5)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
[cause]: Error: ENOENT: no such file or directory, open '/coderabbit-1.markdownlint-cli2.jsonc'
at async open (node:internal/fs/promises:640:25)
at async Object.readFile (node:internal/fs/promises:1287:14)
at async readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:141:17)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/coderabbit-1.markdownlint-cli2.jsonc'
}
}

README.md

markdownlint-cli2 v0.22.1 (markdownlint v0.40.0)
Error: Unable to use configuration file '/coderabbit-0.markdownlint-cli2.jsonc'; ENOENT: no such file or directory, open '/coderabbit-0.markdownlint-cli2.jsonc'
at throwForConfigurationFile (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:48:9)
at readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:169:5)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
[cause]: Error: ENOENT: no such file or directory, open '/coderabbit-0.markdownlint-cli2.jsonc'
at async open (node:internal/fs/promises:640:25)
at async Object.readFile (node:internal/fs/promises:1287:14)
at async readOptionsOrConfig (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:141:17)
at async main (file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2.mjs:927:21)
at async file:///usr/local/lib/node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs:14:22 {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/coderabbit-0.markdownlint-cli2.jsonc'
}
}


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad15d32d47

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cmd/relayfile-cli/main.go
Comment on lines +5739 to +5740
expanded := listenExpandTemplate(runCmd, evt, raw)
cmd := exec.CommandContext(rootCtx, "sh", "-c", expanded)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid shelling raw event fields

When --run is used, the command template is expanded with raw event data and then executed via sh -c. In the documented pattern where {{path}} is embedded inside a quoted prompt, a workspace/provider path containing shell syntax such as /linear/issues/x'; touch /tmp/pwn; '.json escapes the quote and runs locally as soon as the event is received. Pass event fields through environment variables or shell-escape placeholder values before constructing the shell command.

Useful? React with 👍 / 👎.

Comment thread cmd/relayfile-cli/main.go Outdated
if typeFilter != "" {
label += " (" + typeFilter + ")"
}
fmt.Fprintf(stdout, "Listening on %s — Ctrl+C to stop\n", label)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep JSON listen output machine-readable

When --format json is selected, this unconditional banner is still written to stdout before the event stream, so the documented relayfile listen --provider linear --format json | jq '.path' fails on the non-JSON prefix. JSON mode should either suppress human status output or send it to stderr so stdout remains newline-delimited JSON.

Useful? React with 👍 / 👎.

@agent-relay-code

Copy link
Copy Markdown
Contributor

Let me verify two consistency points between README/help and the code: (1) README claims {{path}}, {{type}}, {{provider}}, {{revision}}, {{event}} placeholders — code supports exactly those (no {{timestamp}}). (2) README's relayfile listen with no provider streams "all events from default workspace" — code sets from=now with no path filter, consistent. Both match.

One real consideration worth noting: in text output, {{type}} placeholder is documented in README's prose line ("supports {{path}}, {{type}}...") but the help's --run flag description matches. Everything is consistent.

The review is complete. Let me summarize.

Review Summary — PR #332: relayfile listen command

Purpose: Adds a relayfile listen/watch CLI subcommand that streams live file events over the existing /v1/workspaces/{id}/fs/ws WebSocket and optionally runs a shell command per matching event. Also updates README docs.

Verification performed

  • Build: go build of all three binaries (relayfile, relayfile-mount, relayfile-cli) — OK.
  • Tests: go test ./... (the canonical CI command) — all packages pass, including cmd/relayfile-cli.
  • Vet: go vet ./cmd/relayfile-cli/ — clean.
  • Format: gofmt -l on the changed file — clean.
  • Contract: scripts/check-contract-surface.sh — passes (PR adds a client consumer, not a server handler; no OpenAPI drift).
  • Dependency: nhooyr.io/websocket v1.8.17 + wsjson already in go.mod/go.sum; all imports present.
  • Contract trace: Client listenEvent struct exactly matches server fileEventMessage (internal/httpapi/websocket.go:15). Query params from=now, path, token match the server's parseWebSocketSubscriptionOptions and token-in-query auth (websocket.go:43). Event type strings file.created/updated/deleted match the store's emitted constants. The pong skip is harmless (server only sends pong in reply to a client ping, which this client never sends). The /v1/workspaces/.../fs/... path scheme matches sibling fs endpoints.

Auto-applied fixes

None needed — the diff is already gofmt-clean, vet-clean, and builds.

Addressed comments

  • No bot or human review comments were present in .workforce/context.json or the .workforce/ directory; none to address.

Advisory Notes

  • No test coverage for the new command. runListen and listenExpandTemplate have no unit tests. Adding tests is a human decision, so I left the tree unchanged. Suggest a test for listenExpandTemplate (placeholder expansion) and for --event/type filtering logic.
  • --run executes via sh -c with server-supplied event fields interpolated into the command template. This is intentional/opt-in (the user authors the template), and mirrors the documented design, so no change is warranted. Worth being aware that {{path}}/{{provider}} values originate from server events and are passed to a shell; users should treat workspace contents as they would any untrusted input when crafting --run commands.
  • Token passed as a WebSocket query parameter (q.Set("token", ...)). The code comment notes the server doesn't yet support Authorization on upgrade, and the server indeed reads the token from the query (websocket.go:43), so this matches the existing contract. The client also sends the Authorization: Bearer header redundantly; harmless. Query-string tokens can leak into server/proxy logs — a non-blocking note for a future server-side upgrade-auth improvement, outside this PR's scope.

The PR is mechanically sound, builds, and passes the full test suite locally. No required CI checks could be confirmed as completed/passing from within this sandbox (E2E, SDK, and workers jobs run in CI), and the merge/mergeable state is a post-harness GitHub fact I cannot verify here, so I am not printing READY.

The --path flag can filter far below the provider level using the
alias views already present in every provider tree: by-state/, by-label/,
by-epic/, by-name/, by-id/, and more.

Updated both printListenUsage and README to show concrete examples:
  - Linear issues filtered by state (triage, in-progress)
  - GitHub PRs filtered by label and specific repo
  - Notion edits scoped to a single database
  - Slack events scoped to a specific channel (incidents, etc.)
  - HubSpot contacts vs deals as separate paths
  - Asana tasks scoped to a named project
  - Shortcut stories filtered by epic

Help text closes with a note that alias views are discoverable via
'relayfile tree / --depth 3', so users can find what's available
for their own workspace without reading docs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PF9GK3mPDyWEKXpaJ8ReB9
@agent-relay-code

Copy link
Copy Markdown
Contributor

ℹ️ pr-reviewer: review only — no file changes were applied to the PR (nothing to commit after review). The notes below are advisory and were not pushed.

Review complete. No code changes were needed — the PR is mechanically clean (gofmt-formatted, vet-clean) and builds/tests green. Below is my review.


Review: PR #332relayfile listen reactive local command

Summary of change: Adds a relayfile listen (alias watch) CLI subcommand that opens the existing /v1/workspaces/{id}/fs/ws WebSocket, filters events by --provider/--path/--event, and either prints them (--format text|json) or runs a templated shell command per event (--run with {{path}}, {{type}}, {{provider}}, {{revision}}, {{event}}). README and CLI help text updated. Scope is purely additive and CLI-only.

Verification (the way CI runs it):

  • go build ./cmd/relayfile, ./cmd/relayfile-mount, ./cmd/relayfile-cli — all exit 0.
  • go vet ./cmd/relayfile-cli/ — clean.
  • gofmt -l cmd/relayfile-cli/main.go — no diff.
  • go test ./... — 0 FAIL, all packages pass.
  • scripts/check-contract-surface.sh — passed (no server change, so OpenAPI spec is untouched and correct).

Correctness checks I traced:

  • listenEvent wire struct exactly mirrors the server's fileEventMessage in internal/httpapi/websocket.go:15 (field names + JSON tags match).
  • WS URL construction (cmd/relayfile-cli/main.go:5720) matches the path used by wsinvalidate.go:200 and mountsync/syncer.go:6377, and the from=now / path query params match the OpenAPI contract (openapi/relayfile-v1.openapi.yaml:109).
  • nhooyr.io/websocket v1.8.17 and wsjson are already in go.mod; no dependency drift.
  • watch is not a pre-existing command, so the alias introduces no dispatch/help collision.
  • Helpers (prepareWorkspaceCommandClient, normalizeFlagArgs) used correctly with existing signatures.

No safety-critical, lifecycle, or fail-closed/fail-open logic is touched.

Addressed comments

  • No bot or human review comments were present in .workforce/context.json (the PR had no review threads in the provided metadata, and there is no comments file in .workforce/). Nothing to reconcile against the current checkout.

Advisory Notes

These are non-blocking observations, left as code unchanged (out of scope for a mechanical fix and not demonstrated bugs):

  • Token sent twice. runListen puts the JWT in both the token query param (main.go:5736) and an Authorization: Bearer header (main.go:5742). The query param is required by the server for the WS upgrade per the contract, so this is functional. By contrast, WSInvalidator.websocketURL (internal/mountfuse/wsinvalidate.go:201) deliberately keeps the token out of the query string to avoid leaking it into proxy/access logs. If credential-in-URL logging is a concern, a maintainer may want to align listen with the header-only approach once the server accepts Authorization on upgrade — but that requires a server change and a human decision.
  • {{event}} / --run injection surface. --run is executed via sh -c with template placeholders substituted from server-delivered event fields (e.g. {{path}}). A path containing shell metacharacters would be interpreted by the shell. This mirrors how the documented examples intend it to be used, and the input originates from the user's own authenticated workspace, so it's not a new privilege boundary — but worth a maintainer's awareness if untrusted providers can influence paths.

Both are design considerations, not regressions, and neither blocks merge.

The PR is additive, self-contained, builds and tests green, and has no merge-conflict or contract impact. I did not change any files. I'm not printing READY because I cannot confirm the live CI checks have completed and are passing (that's a post-harness action), and there is no human decision currently blocked on me.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
README.md (1)

142-142: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the text/json output format toggle for eventless mode.

Per PR objectives, when --run is not set, events are printed in either text or json format (default: text). The README should document the flag or mechanism for selecting output format. Additionally, the template-variable list omits eventId, contentHash, origin, correlationId, and timestamp, which are available on FilesystemEvent but may be useful for advanced use cases.

📝 Suggested documentation additions

Update line 142 to cover the missing pieces:

`--run` supports `{{path}}`, `{{type}}`, `{{provider}}`, `{{revision}}`, `{{eventId}}`, `{{contentHash}}`, `{{origin}}`, `{{correlationId}}`, `{{timestamp}}`, and `{{event}}` (full JSON). When `--run` is not set, events are printed in text format by default; pass `--json` to print raw JSON events instead. The alias views available in each provider tree (`by-state/`, `by-label/`, `by-epic/`, `by-name/`, …) are discoverable with `relayfile tree / --depth 3`.

Or, if the format flag is different, adjust the wording to reflect the actual CLI surface.

To verify that the output format flag exists and works as described, please check the relayfile listen implementation in cmd/relayfile-cli/main.go and confirm:

  1. The exact flag name for toggling text/json output (e.g., --json, --format json, etc.)
  2. Whether all template variables listed above are actually supported.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 142, Update the README.md documentation at line 142 to
include the complete list of template variables supported by `--run` (currently
missing eventId, contentHash, origin, correlationId, and timestamp) and add
documentation for the output format toggle mechanism when `--run` is not set
(e.g., the --json flag for printing raw JSON events instead of the default text
format). Verify the exact flag name by checking the relayfile listen
implementation in cmd/relayfile-cli/main.go to ensure the documentation
accurately reflects the CLI interface.
cmd/relayfile-cli/main.go (1)

40-41: 📐 Maintainability & Code Quality | 🔵 Trivial

Consider updating nhooyr.io/websocket imports to github.com/coder/websocket.

The library moved to github.com/coder/websocket in 2024 and is no longer maintained at the original vanity import path. The API remains unchanged, but the import statement must be updated and reflected in go.mod. This is a codebase-wide change beyond the scope of this PR.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/relayfile-cli/main.go` around lines 40 - 41, Replace the two import
statements for the websocket library in the import block. Change
"nhooyr.io/websocket" to "github.com/coder/websocket" and update
"nhooyr.io/websocket/wsjson" to use the new import path from
github.com/coder/websocket instead, since the library has been relocated and is
now maintained at the GitHub path. This import change should also be reflected
in the go.mod file to ensure the correct dependency version is fetched.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/relayfile-cli/main.go`:
- Around line 5785-5793: The listenExpandTemplate function directly interpolates
untrusted server-delivered values (evt.Path, evt.Provider, evt.Type,
evt.Revision, and raw event JSON) into the runCmd string which is then executed
via sh -c, creating a command injection vulnerability. Refactor to pass event
data as environment variables instead of string interpolation by modifying how
listenExpandTemplate works and how the exec.CommandContext call is constructed
with the expanded command. Update the command execution to use environment
variable references like $RELAYFILE_PATH instead of template placeholders like
{{path}}, and ensure printListenUsage and documentation reflect this change. If
the template API must be preserved, shell-quote every substituted value before
insertion into the command string.

---

Nitpick comments:
In `@cmd/relayfile-cli/main.go`:
- Around line 40-41: Replace the two import statements for the websocket library
in the import block. Change "nhooyr.io/websocket" to
"github.com/coder/websocket" and update "nhooyr.io/websocket/wsjson" to use the
new import path from github.com/coder/websocket instead, since the library has
been relocated and is now maintained at the GitHub path. This import change
should also be reflected in the go.mod file to ensure the correct dependency
version is fetched.

In `@README.md`:
- Line 142: Update the README.md documentation at line 142 to include the
complete list of template variables supported by `--run` (currently missing
eventId, contentHash, origin, correlationId, and timestamp) and add
documentation for the output format toggle mechanism when `--run` is not set
(e.g., the --json flag for printing raw JSON events instead of the default text
format). Verify the exact flag name by checking the relayfile listen
implementation in cmd/relayfile-cli/main.go to ensure the documentation
accurately reflects the CLI interface.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 094d597f-32a7-40fd-b876-18f17cb6ffa4

📥 Commits

Reviewing files that changed from the base of the PR and between 60d54c6 and 1c66147.

📒 Files selected for processing (2)
  • README.md
  • cmd/relayfile-cli/main.go

Comment thread cmd/relayfile-cli/main.go
claude added 6 commits June 23, 2026 03:38
- `relayfile dev`: zero-friction entry point that checks auth/status then
  hands off to the listen loop; prints a setup prompt if not connected
- `relayfile listen --background`: detaches to background, logs to
  ~/.relayfile/listen.log; `--daemonized` internal flag for child process
- Added listenPIDFile, listenLogFile, spawnBackgroundListenProcess helpers
- Wired `dev` into run() switch, printHelpForArgs, printUsage
- README: documents background mode, relayfile dev, cloud webhook note (#3)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PF9GK3mPDyWEKXpaJ8ReB9
Lead with the reactive loop — event fires, files already synced, agent
acts without API calls — rather than presenting filesystem and events as
separate features. Cut from 409 to 106 lines: removed evals, SDK internals,
mount layout diagram, connection migration guide, verbose multi-agent section,
and operational details that belong in linked docs. Sharpened the Hookdeck
comparison to name the context gap explicitly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PF9GK3mPDyWEKXpaJ8ReB9
Writes a systemd user unit (Linux) or launchd plist (macOS) so the
listen daemon survives reboots without manual --background management.

  relayfile supervisor install --path "/linear/issues/by-state/triage/**" \
    --event file.created --run "claude --print '...'"
  relayfile supervisor status
  relayfile supervisor uninstall

- systemd: ~/.config/systemd/user/relayfile-listen.service; enables and
  starts via systemctl --user, RestartSec=5s on failure
- launchd: ~/Library/LaunchAgents/com.relayfile.listen.plist; loaded with
  KeepAlive=true so macOS restarts it on crash and at login
- All relayfile listen flags pass through verbatim into the unit file
- shelljoin quotes args with spaces/metacharacters for ExecStart
- plistEscapeXML handles XML entities in plist string values

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PF9GK3mPDyWEKXpaJ8ReB9
The README rewrite cut the TypeScript SDK path. Add it back as a
first-class section with ensureMountedWorkspace, error types, and a
link to the post-auth-mount-session guide.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PF9GK3mPDyWEKXpaJ8ReB9
Events-based pricing: free (10k/mo), starter $49 (100k/mo),
team $199 (500k/mo), enterprise custom. Covers what counts as an event,
overage rates, BYO connections for enterprise, and hosted vs self-hosted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PF9GK3mPDyWEKXpaJ8ReB9
Events alone undersells the product — relayfile also stores the full
live state of every connected provider. Added storage tier (1/10/50 GB),
storage overage ($0.25/GB/mo), and a plain-English explanation of what
storage covers and typical usage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PF9GK3mPDyWEKXpaJ8ReB9

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/relayfile-cli/main.go`:
- Around line 6047-6049: The code constructs execArgs with listenArgs without
validating that forbidden flags are present, which can cause the supervisor
service to crash and restart in a loop if a user passes --background or
--daemonized. Before appending listenArgs to execArgs (before the shelljoin
call), add validation logic that checks if listenArgs contains either the
--background or --daemonized flag and returns an error with a clear message
explaining these flags are incompatible with supervisor installation. This
validation should apply to all locations where listenArgs are used to construct
the listen command in supervisor install contexts.
- Around line 5765-5769: The issue is that log file rotation happens twice: once
in the parent process before daemonizing, and again in the child when it
re-enters runListen with the daemonized flag set. This double rotation causes
the daemon's stdout/stderr file descriptor to point to a rotated file instead of
the active listen.log, breaking log visibility. Remove the rotateLogFile call in
the daemonized conditional block (the if statement checking *daemonized at lines
5765-5769) since the parent process already handles rotation before opening the
listen.log file descriptor for the child.
- Around line 6216-6225: The shelljoin function detects dollar signs as special
characters and wraps arguments in double quotes, but does not escape the dollar
signs themselves. In systemd's ExecStart, variable expansion occurs even within
double quotes, so literal dollar signs must be escaped as double dollar signs.
Modify the shelljoin function to additionally replace each dollar sign with two
dollar signs (using strings.ReplaceAll similar to how backslashes and quotes are
already being escaped) before wrapping the argument in quotes. This ensures that
when the quoted argument is processed by systemd, the dollar signs remain
literal rather than being interpreted as variable expansion specifiers.
- Around line 5918-5919: The spawnBackgroundListenProcess function passes a
--pid-file flag to the child process, but the runListen function does not
register this flag in its flag set (near line 5735) or include it in the
normalizeFlagArgs map (lines 5741-5751), causing fs.Parse() to reject it and the
child to fail immediately. Additionally, writeDaemonPIDState is never called to
write the PID to the file. Fix this by either registering the pid-file string
flag in runListen's flag set, adding it to the normalizeFlagArgs map, and
calling writeDaemonPIDState when daemonized to persist the PID; or remove the
--pid-file argument from spawnBackgroundListenProcess entirely if PID tracking
is not yet required.

In `@README.md`:
- Around line 55-56: The grep command pattern '"state":"Todo"' is searching for
minified JSON without spaces, but the JSON examples shown in the README use
pretty-printed format with spaces around colons. Update the grep pattern to
include the space after the colon to match the documented JSON format, changing
the search string from the minified version to include the space that appears in
the actual example output above it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 818dae11-90d0-4889-90c2-907e7ded0b04

📥 Commits

Reviewing files that changed from the base of the PR and between 1c66147 and ab0b669.

📒 Files selected for processing (3)
  • README.md
  • cmd/relayfile-cli/main.go
  • docs/guides/pricing.md
✅ Files skipped from review due to trivial changes (1)
  • docs/guides/pricing.md

Comment thread cmd/relayfile-cli/main.go
Comment on lines +5765 to +5769
if *daemonized {
if err := rotateLogFile(listenLogFile()); err != nil {
return err
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Double log rotation sends daemon output to the wrong file.

The parent already calls rotateLogFile(logFile) and opens listen.log as the child's stdout/stderr (Lines 5903, 5920-5927). When the child re-enters runListen with --daemonized, it rotates again here, renaming the freshly-opened listen.log. Because the child's stdout fd is still bound to that now-renamed file, all daemon output lands in the rotated file and ~/.relayfile/listen.log stays empty — breaking the documented log location and relayfile logs.

Rotate in exactly one place. Since the parent already rotates before opening the handle, drop the rotation in the daemonized branch.

🪵 Proposed fix
-	if *daemonized {
-		if err := rotateLogFile(listenLogFile()); err != nil {
-			return err
-		}
-	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if *daemonized {
if err := rotateLogFile(listenLogFile()); err != nil {
return err
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/relayfile-cli/main.go` around lines 5765 - 5769, The issue is that log
file rotation happens twice: once in the parent process before daemonizing, and
again in the child when it re-enters runListen with the daemonized flag set.
This double rotation causes the daemon's stdout/stderr file descriptor to point
to a rotated file instead of the active listen.log, breaking log visibility.
Remove the rotateLogFile call in the daemonized conditional block (the if
statement checking *daemonized at lines 5765-5769) since the parent process
already handles rotation before opening the listen.log file descriptor for the
child.

Comment thread cmd/relayfile-cli/main.go
Comment on lines +5918 to +5919
childArgs := append([]string{"listen"}, filtered...)
childArgs = append(childArgs, "--daemonized", "--pid-file", pidFile)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for any pid-file flag registration and inspect normalizeFlagArgs.
rg -nP '"pid-file"|pid-file|pidFile' cmd/relayfile-cli/main.go
echo "---- normalizeFlagArgs impl ----"
ast-grep --pattern 'func normalizeFlagArgs($$$) $_ {
  $$$
}'

Repository: AgentWorkforce/relayfile

Length of output: 3265


🏁 Script executed:

sed -n '5730,5770p' cmd/relayfile-cli/main.go

Repository: AgentWorkforce/relayfile

Length of output: 1776


🏁 Script executed:

sed -n '5900,5930p' cmd/relayfile-cli/main.go

Repository: AgentWorkforce/relayfile

Length of output: 1101


🏁 Script executed:

# Check if pidFile is ever written in the listen command path
rg -n 'writeDaemonPIDState|pidFile.*Write|ioutil.WriteFile.*pidFile' cmd/relayfile-cli/main.go | head -20

Repository: AgentWorkforce/relayfile

Length of output: 297


🏁 Script executed:

# Search for writeDaemonPIDState usage specifically in listen-related code
sed -n '5730,5900p' cmd/relayfile-cli/main.go | grep -n "writeDaemonPIDState\|pidFile"

Repository: AgentWorkforce/relayfile

Length of output: 259


🏁 Script executed:

# Check what happens after daemonized flag in runListen
sed -n '5768,5850p' cmd/relayfile-cli/main.go

Repository: AgentWorkforce/relayfile

Length of output: 2858


Background mode is broken: the daemonized child is launched with an unregistered --pid-file flag.

spawnBackgroundListenProcess re-invokes the binary with listen … --daemonized --pid-file <path>, but runListen's flag set (line 5735) does not register a pid-file string flag, and its normalizeFlagArgs map (lines 5741-5751) does not list it. When the detached child re-enters runListen, fs.Parse() (which uses flag.ContinueOnError) will reject the unknown --pid-file flag and return an error, causing the child to exit immediately — so relayfile listen --background starts a process that dies on launch. Additionally, the listen command never calls writeDaemonPIDState, so even if the process survived, nothing would be written to the PID file.

Either register a pid-file string flag in runListen, write the PID to it once daemonized, or drop the --pid-file argument from spawnBackgroundListenProcess if PID tracking is not yet implemented.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/relayfile-cli/main.go` around lines 5918 - 5919, The
spawnBackgroundListenProcess function passes a --pid-file flag to the child
process, but the runListen function does not register this flag in its flag set
(near line 5735) or include it in the normalizeFlagArgs map (lines 5741-5751),
causing fs.Parse() to reject it and the child to fail immediately. Additionally,
writeDaemonPIDState is never called to write the PID to the file. Fix this by
either registering the pid-file string flag in runListen's flag set, adding it
to the normalizeFlagArgs map, and calling writeDaemonPIDState when daemonized to
persist the PID; or remove the --pid-file argument from
spawnBackgroundListenProcess entirely if PID tracking is not yet required.

Comment thread cmd/relayfile-cli/main.go
Comment on lines +6047 to +6049
execArgs := []string{executable, "listen"}
execArgs = append(execArgs, listenArgs...)
execStart := shelljoin(execArgs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Reject --background/--daemonized in supervisor install flags.

Both the systemd unit (Type=simple) and launchd plist (KeepAlive) expect the listen process to stay in the foreground. If a user passes --background, the embedded relayfile listen --background detaches and exits, and the supervisor will treat the service as crashed and restart it in a loop. Consider validating listenArgs and erroring early if --background/--daemonized is present.

Also applies to: 6099-6099

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/relayfile-cli/main.go` around lines 6047 - 6049, The code constructs
execArgs with listenArgs without validating that forbidden flags are present,
which can cause the supervisor service to crash and restart in a loop if a user
passes --background or --daemonized. Before appending listenArgs to execArgs
(before the shelljoin call), add validation logic that checks if listenArgs
contains either the --background or --daemonized flag and returns an error with
a clear message explaining these flags are incompatible with supervisor
installation. This validation should apply to all locations where listenArgs are
used to construct the listen command in supervisor install contexts.

Comment thread cmd/relayfile-cli/main.go
Comment on lines +6216 to +6225
func shelljoin(args []string) string {
quoted := make([]string, len(args))
for i, a := range args {
if strings.ContainsAny(a, " \t\"'\\$`{}[]|&;<>()#~!") {
a = "\"" + strings.ReplaceAll(a, "\"", "\\\"") + "\""
}
quoted[i] = a
}
return strings.Join(quoted, " ")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Does systemd ExecStart expand $VAR and ${VAR} inside double-quoted arguments, and is $$ required to escape a literal dollar sign?

💡 Result:

Yes, systemd performs environment variable expansion for $VAR and ${VAR} within ExecStart lines, regardless of whether they are enclosed in double quotes [1]. Because systemd's parsing logic happens before the command is executed by the shell, it interprets these variables and substitutes them with their values from the systemd environment [2][3][1]. To prevent systemd from expanding a dollar sign and instead pass a literal dollar sign (e.g., to be interpreted later by a shell like bash), you must use double dollar signs ($$) [1][4][5]. Key points regarding this behavior: - Expansion happens systemd-side: Even if you quote the argument in your unit file, systemd processes the content first [3]. - ${VAR} vs $VAR: Using ${VAR} replaces the variable with its value as a single argument (preserving whitespace), whereas using $VAR as a separate word splits the value by whitespace into zero or more arguments [1]. - Literal escaping: Using $$ is the required method to escape a literal dollar sign, ensuring that the resulting string passed to the actual process contains a single $ [2][1]. If you are running a command via a shell (e.g., /bin/sh -c "...") and need the shell to perform the expansion instead of systemd, you should escape all dollar signs with $$ [2][3][4]. For example, use echo $$VAR to ensure the literal $VAR is passed to the shell, which will then expand it using its own environment [3].

Citations:


🏁 Script executed:

# First, find the file and understand its context
fd "main.go" | grep -i relay

# Search for shelljoin usage
rg "shelljoin" -B3 -A3

Repository: AgentWorkforce/relayfile

Length of output: 238


🏁 Script executed:

# Search for shelljoin usage in the codebase
rg "shelljoin" -A5 -B5

# Also check if the file has both shelljoin definition and its usages
rg "shelljoin|ExecStart" cmd/relayfile-cli/main.go -B2 -A2

Repository: AgentWorkforce/relayfile

Length of output: 292


🏁 Script executed:

# Check the actual content around line 6216-6225 in the file
sed -n '6200,6250p' cmd/relayfile-cli/main.go

# Search more broadly for shelljoin definition
rg "func shelljoin" -A10

# Search for where arguments or commands are being escaped/quoted
rg "strings.ContainsAny|ReplaceAll.*\\\\\\\"|quoted" cmd/relayfile-cli/main.go -B2 -A2

Repository: AgentWorkforce/relayfile

Length of output: 1808


🏁 Script executed:

# Now find where shelljoin is called/used
rg "shelljoin\(" -B5 -A5

# Get the total lines in the file to understand context
wc -l cmd/relayfile-cli/main.go

# Search for systemd-related code (ExecStart, unit files, etc)
rg -i "execstart|systemd|unit.*file|supervisor" cmd/relayfile-cli/main.go -B2 -A2 | head -100

Repository: AgentWorkforce/relayfile

Length of output: 162


🏁 Script executed:

# Check the log file to see what happened
cat /tmp/coderabbit-shell-logs/shell-output-aAlZw1

# Try simpler search for shelljoin calls
grep -n "shelljoin(" cmd/relayfile-cli/main.go

# Search for ExecStart usage
grep -n "ExecStart" cmd/relayfile-cli/main.go

# Get file size first
wc -l cmd/relayfile-cli/main.go

Repository: AgentWorkforce/relayfile

Length of output: 456


🏁 Script executed:

# Look at the context around line 6049 where shelljoin is called
sed -n '6030,6080p' cmd/relayfile-cli/main.go

# Also check what execArgs contains and how this ExecStart is used
sed -n '6000,6100p' cmd/relayfile-cli/main.go

Repository: AgentWorkforce/relayfile

Length of output: 4713


🏁 Script executed:

# Check what listenArgs could contain and where it comes from
sed -n '5900,6000p' cmd/relayfile-cli/main.go | head -100

# Search for where listenArgs is populated in "listen" command
grep -n "func.*listen\|listenArgs\|--" cmd/relayfile-cli/main.go | head -50

# Check if there's any handling of environment variables in arguments
grep -n "\$\|environment\|ENV" cmd/relayfile-cli/main.go | head -30

Repository: AgentWorkforce/relayfile

Length of output: 7981


shelljoin does not escape $ for systemd's ExecStart expansion, breaking arguments containing dollar signs.

systemd performs variable and specifier expansion on ExecStart even within double quotes; literal $ must be written as $$ to prevent expansion. When a user provides a --run argument containing $VAR or ${VAR}, shelljoin will double-quote it (detecting $ as a special character) but will not escape the $ itself. systemd will then expand the variable before the process sees it, producing a broken or unintended command. Consider escaping $$$ in shelljoin (or handle it specifically in supervisorInstallSystemd). The launchd path is unaffected as it passes arguments as array elements in the plist file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/relayfile-cli/main.go` around lines 6216 - 6225, The shelljoin function
detects dollar signs as special characters and wraps arguments in double quotes,
but does not escape the dollar signs themselves. In systemd's ExecStart,
variable expansion occurs even within double quotes, so literal dollar signs
must be escaped as double dollar signs. Modify the shelljoin function to
additionally replace each dollar sign with two dollar signs (using
strings.ReplaceAll similar to how backslashes and quotes are already being
escaped) before wrapping the argument in quotes. This ensures that when the
quoted argument is processed by systemd, the dollar signs remain literal rather
than being interpreted as variable expansion specifiers.

Comment thread README.md
Comment on lines +55 to 56
$ grep -l '"state":"Todo"' mount/linear/issues/*.json
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the grep example match the shown JSON.

grep -l '"state":"Todo"' only matches minified JSON. The example above shows spaces around the colon, so this command will miss the documented output.

Suggested fix
- $ grep -l '"state":"Todo"' mount/linear/issues/*.json
+ $ grep -l '"state"[[:space:]]*:[[:space:]]*"Todo"' mount/linear/issues/*.json
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$ grep -l '"state":"Todo"' mount/linear/issues/*.json
```
$ grep -l '"state"[[:space:]]*:[[:space:]]*"Todo"' mount/linear/issues/*.json
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 55 - 56, The grep command pattern '"state":"Todo"' is
searching for minified JSON without spaces, but the JSON examples shown in the
README use pretty-printed format with spaces around colons. Update the grep
pattern to include the space after the colon to match the documented JSON
format, changing the search string from the minified version to include the
space that appears in the actual example output above it.

claude added 2 commits June 23, 2026 05:51
Raw webhook = diff without context. Relayfile materializes the event —
the file updates before --run fires, full record plus surrounding state
already on disk. Agent wakes into a complete world, not a notification.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PF9GK3mPDyWEKXpaJ8ReB9
Full stack guide for running n8n and relayfile on your own
infrastructure with no data leaving the environment. Covers
relayfile Write node, Trigger node, supervisor install, and
data residency guarantees.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PF9GK3mPDyWEKXpaJ8ReB9
@khaliqgant

Copy link
Copy Markdown
Member Author

Superseded by #334 (merged). The listen command, HTTP/2 ALPN fix, wsEncodeGlob space-encoding fix, and client-side path filter are all on main via #334. The README rewrite in this branch conflicts with #331 (merged). Two docs-only files (docs/guides/pricing.md and docs/guides/self-hosted-n8n.md) could be salvaged in a follow-up PR if still wanted.

@khaliqgant khaliqgant closed this Jun 23, 2026
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.

2 participants