Skip to content

feat(factory): auto-start relayfile local mount on factory start (pear#364)#366

Merged
khaliqgant merged 2 commits into
mainfrom
fix/pear-364-factory-auto-mount
Jun 16, 2026
Merged

feat(factory): auto-start relayfile local mount on factory start (pear#364)#366
khaliqgant merged 2 commits into
mainfrom
fix/pear-364-factory-auto-mount

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Jun 15, 2026

Copy link
Copy Markdown
Member

Summary

  • relayfile-binary.ts: resolves the mount binary (env override → repo-root bin/relayfile-mount → dev relayfile/dist fallbacks with platform/arch mapping); checkMountStaleness(stateFilePath, workspaceId) checks workspace mismatch, stale lastReconcileAt (>15 min), and dead pid
  • local-mount-preflight.ts: ensureLocalMount(workspaceId, startDir) — spawns the mount binary when .integrations/.relay/state.json is missing or unparseable; warns to stderr when stale (no auto-restart); auth errors throw actionable messages pointing to relayfile workspace join
  • fleet.ts: calls ensureLocalMount at the top of the factory start handler (before signal handlers); injectable via FleetCliDeps.ensureLocalMount for test override

Test plan

  • npx tsc -p packages/factory-sdk/tsconfig.json --noEmit passes
  • npx vitest run packages/factory-sdk/src/mount/relayfile-binary.test.ts passes
  • factory start with no state file → mount binary is spawned
  • factory start with stale state file → warning logged to stderr, factory continues
  • factory start with auth error from binary → throws with actionable message

🤖 Generated with Claude Code

Review in cubic

…r#364)

- Add `relayfile-binary.ts`: resolves the mount binary (env override →
  repo-root bin/ → dev relayfile/dist fallbacks) and `checkMountStaleness`
  (workspace mismatch, stale timestamp, dead pid → stale=true with reason)
- Add `local-mount-preflight.ts`: `ensureLocalMount(workspaceId, startDir)`
  spawns the binary when state.json is missing/unparseable; warns to stderr
  when mount is stale; auth errors surface as actionable thrown messages
- Wire `ensureLocalMount` into `fleet.ts` `factory start` handler before
  `factory.start()`, injectable via `FleetCliDeps` for test override

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@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 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@khaliqgant, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 44 minutes and 49 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e34631a-9db8-4f64-b339-a7a27b8db250

📥 Commits

Reviewing files that changed from the base of the PR and between 9fbdc2e and b26f5ae.

📒 Files selected for processing (5)
  • packages/factory-sdk/src/cli/fleet.test.ts
  • packages/factory-sdk/src/mount/local-mount-preflight.test.ts
  • packages/factory-sdk/src/mount/local-mount-preflight.ts
  • packages/factory-sdk/src/mount/relayfile-binary.test.ts
  • packages/factory-sdk/src/mount/relayfile-binary.ts
📝 Walkthrough

Walkthrough

Adds a local-mount preflight system to the factory SDK. A new relayfile-binary.ts module resolves the relayfile-mount binary and checks mount state staleness. A new local-mount-preflight.ts orchestrates spawning, polling, and staleness warnings. The fleet.ts CLI wires the preflight into the factory start command via an injectable dependency.

Changes

Local Mount Preflight

Layer / File(s) Summary
Binary resolution and mount staleness
packages/factory-sdk/src/mount/relayfile-binary.ts
Adds resolveRelayfileMountBinary (platform/arch naming, pear-root discovery, env override, executable validation) and checkMountStaleness (workspace ID check, timestamp freshness, process.kill(pid, 0) liveness probe returning stale, optional reason, and pid).
ensureLocalMount preflight orchestrator
packages/factory-sdk/src/mount/local-mount-preflight.ts
Adds ensureLocalMount which checks for an existing state file, spawns the mount binary and polls until state appears when absent, and emits a stderr staleness warning when stale. Includes spawnMount (auth-error detection, exit-code rejection), waitForStateFile (deadline polling), and helpers isAuthError/sleep.
Fleet CLI wiring
packages/factory-sdk/src/cli/fleet.ts
Imports ensureLocalMount, adds optional ensureLocalMount to FleetCliDeps for injection, and calls it with workspaceId and cwd before factory start proceeds.
Tests
packages/factory-sdk/src/mount/relayfile-binary.test.ts
Vitest coverage for resolveRelayfileMountBinary (env override to executable) and checkMountStaleness across missing state, workspace mismatch, stale timestamp, dead PID via mocked process.kill, and the fresh/alive non-stale path.

Sequence Diagram(s)

sequenceDiagram
  participant FleetCLI as fleet.ts runFactoryCommand
  participant ELM as ensureLocalMount
  participant IMP as isMountStatePresent
  participant SM as spawnMount
  participant WSF as waitForStateFile
  participant CMS as checkMountStaleness

  FleetCLI->>ELM: workspaceId, cwd
  ELM->>IMP: stateFilePath
  IMP-->>ELM: present: boolean

  alt state file absent
    ELM->>SM: resolvedBinary, args
    SM-->>ELM: resolve / reject(auth error / exit code)
    ELM->>WSF: stateFilePath, 10s deadline
    WSF-->>ELM: resolve / timeout error
  else state file present
    ELM->>CMS: stateFilePath, workspaceId
    CMS-->>ELM: stale, reason, pid
    alt stale
      ELM->>ELM: write warning to process.stderr
    end
  end

  ELM-->>FleetCLI: void
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • AgentWorkforce/pear#245: Both PRs modify the factory start startup flow in runFactoryCommand inside fleet.ts, with this PR inserting a local-mount preflight before execution.
  • AgentWorkforce/pear#267: Both PRs touch fleet.ts command routing around the factory start path, with the retrieved PR bypassing the mount path for reap-orphans and this PR adding a preflight gate.

Suggested labels

no-agent-relay-review

🐇 A rabbit once hopped to a mount so stale,
The relayfile binary had gone off the trail.
Now preflight checks shine with a PID and a chime,
Staleness is flagged, and the spawning's on time!
No mount left behind — every start is just fine. 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature: automatic startup of the relayfile local mount when factory starts, with a reference to the issue number.
Description check ✅ Passed The description is well-related to the changeset, providing clear details about the three main components and their functionality that align with the actual code changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 fix/pear-364-factory-auto-mount

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 and usage tips.

@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: 9fbdc2e119

ℹ️ 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".

throw new Error(NOT_FOUND_ERROR)
}

const pearRoot = findPearRoot(__dirname)

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 Resolve the bundled mount binary from the repo root

When RELAYFILE_MOUNT_BIN is not set, this starts discovery from the module directory and only accepts an ancestor containing factory.config.json as the Pear root. In the normal repo/app layout the installed binary is under bin/relayfile-mount, while factory.config.json is a user factory config and is not an ancestor of packages/factory-sdk/src/mount, so pearRoot becomes null and factory start fails with “relayfile-mount binary not found” before it can auto-start the mount. Use a package/repo marker or pass the launcher/repo root instead.

Useful? React with 👍 / 👎.

Comment on lines +72 to +80
while (Date.now() < deadline) {
try {
await readFile(stateFilePath, 'utf8')
return
} catch {
// not yet present
}
await sleep(200)
}

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 Fail when the mount never writes state

If relayfile-mount start --background exits 0 but the daemon never creates .integrations/.relay/state.json within this timeout, the loop simply falls through and ensureLocalMount returns success. In that slow-start/wrong-cwd/daemon-crash case factory.start runs with no local mount and no warning, so writeback can be silently lost; throw or at least warn on timeout instead of treating absence as success.

Useful? React with 👍 / 👎.

Comment on lines +112 to +116
const lastReconcileAt = typeof parsed.lastReconcileAt === 'string'
? Date.parse(parsed.lastReconcileAt)
: NaN
if (!Number.isFinite(lastReconcileAt)) {
return { stale: true, reason: 'last reconcile timestamp is missing', pid }

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 Accept the timestamp field relayfile-mount writes

The existing Relayfile mount state reader in src/main/integration-mounts.ts checks lastSuccessfulReconcileAt from .relay/state.json; a healthy mount state with that field but no lastReconcileAt reaches this branch and is reported stale with “last reconcile timestamp is missing”. That makes every factory start warn that writeback may not propagate even though the mount is fresh, hiding the real stale-mount signal; parse lastSuccessfulReconcileAt here as well.

Useful? React with 👍 / 👎.

@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: 2

🧹 Nitpick comments (1)
packages/factory-sdk/src/mount/relayfile-binary.test.ts (1)

37-48: ⚡ Quick win

Add a resolver test for repo-root bin candidate on win32.

Current tests don’t cover the platform-specific repo-root candidate path, so the Windows .exe regression can slip through. A small vi.spyOn(process, 'platform', 'get')-style platform-branch test (or equivalent abstraction seam) would lock this behavior.

🤖 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 `@packages/factory-sdk/src/mount/relayfile-binary.test.ts` around lines 37 -
48, Add a new test case in the describe block for resolveRelayfileMountBinary
that covers the platform-specific repo-root candidate path for Windows. Use
vi.spyOn(process, 'platform', 'get') to mock the platform as 'win32', and then
verify that resolveRelayfileMountBinary() correctly resolves to the repo-root
bin candidate with the appropriate .exe extension handling. This test should run
alongside the existing RELAYFILE_MOUNT_BIN test to ensure Windows-specific
behavior is covered and prevent .exe-related regressions.
🤖 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 `@packages/factory-sdk/src/mount/local-mount-preflight.ts`:
- Around line 70-81: The waitForStateFile function has two issues: it silently
falls through without throwing an error when the timeout deadline is reached
(after the while loop ends), allowing ensureLocalMount to continue even when
mount startup failed; and it returns successfully upon reading any file without
validating that the file contents are actually valid/well-formed state data. Fix
this by throwing an error when Date.now() exceeds the deadline, and by
validating the contents of the successfully read file (such as checking if it
can be parsed as valid state data) before returning, so that malformed state
files are rejected.

In `@packages/factory-sdk/src/mount/relayfile-binary.ts`:
- Around line 69-71: The hardcoded binary name `relayfile-mount` on line 70 does
not include the platform-specific executable extension, causing Windows systems
to fail discovering the valid `relayfile-mount.exe` binary in the `bin/`
directory. Replace the hardcoded string with a platform-aware binary name
construction that appends `.exe` on Windows and uses the plain name on other
platforms. This should be applied to the repo-root candidate path being joined
with `pearRoot`, 'bin', and the binary name.

---

Nitpick comments:
In `@packages/factory-sdk/src/mount/relayfile-binary.test.ts`:
- Around line 37-48: Add a new test case in the describe block for
resolveRelayfileMountBinary that covers the platform-specific repo-root
candidate path for Windows. Use vi.spyOn(process, 'platform', 'get') to mock the
platform as 'win32', and then verify that resolveRelayfileMountBinary()
correctly resolves to the repo-root bin candidate with the appropriate .exe
extension handling. This test should run alongside the existing
RELAYFILE_MOUNT_BIN test to ensure Windows-specific behavior is covered and
prevent .exe-related regressions.
🪄 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: a5ccec4e-f50c-4de9-a091-8959a6314f00

📥 Commits

Reviewing files that changed from the base of the PR and between ee554aa and 9fbdc2e.

📒 Files selected for processing (4)
  • packages/factory-sdk/src/cli/fleet.ts
  • packages/factory-sdk/src/mount/local-mount-preflight.ts
  • packages/factory-sdk/src/mount/relayfile-binary.test.ts
  • packages/factory-sdk/src/mount/relayfile-binary.ts

Comment thread packages/factory-sdk/src/mount/local-mount-preflight.ts Outdated
Comment thread packages/factory-sdk/src/mount/relayfile-binary.ts
@khaliqgant

Copy link
Copy Markdown
Member Author

This bundles the binary? We can't assume local installation or the existence of a binary by default

@khaliqgant

khaliqgant commented Jun 16, 2026

Copy link
Copy Markdown
Member Author

Addressed in b26f5ae. Short answer on the SDK/binary question:

  • We do use the relayfile SDK for the cloud mount client, and the Electron app uses @relayfile/sdk/mount-launcher for lifecycle launching.
  • This factory preflight still needs a concrete local relayfile-mount executable because factory start is a Node CLI path outside the Electron main-process launcher. The preflight only needs to start/verify the local mount before the factory daemon proceeds.
  • The binary should not depend on a global install. @relayfile/sdk brings relayfile-mount via per-platform optional dependency packages like @relayfile/mount-darwin-arm64 / @relayfile/mount-linux-x64. Electron packaging already unpacks node_modules/@relayfile/mount-*/bin/** so those binaries are spawnable from the packaged app.
  • I updated the factory CLI resolver to prefer those SDK optional-package binaries first, then fall back to repo bin/relayfile-mount(.exe) and local relayfile dist paths. So we no longer assume local/global installation or a manually populated repo binary by default.

Also fixed the CI failure by injecting the preflight in daemon-signal tests, fixed Windows .exe repo-bin resolution, and made mount startup wait reject if the state file never becomes well-formed.

@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer could not complete review for #366 in AgentWorkforce/pear.
The review harness exited with code 1.
No review was posted; this needs operator attention.

@khaliqgant khaliqgant merged commit bf17478 into main Jun 16, 2026
5 checks passed
@khaliqgant khaliqgant deleted the fix/pear-364-factory-auto-mount branch June 16, 2026 08:24
@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer could not complete review for #366 in AgentWorkforce/pear.
The review harness exited with code 1.
No review was posted; this needs operator attention.

@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.

pr-reviewer could not complete review for #366 in AgentWorkforce/pear.
The review harness exited with code 1.
No review was posted; this needs operator attention.

@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer could not complete review for #366 in AgentWorkforce/pear.
The review harness exited with code 1.
No review was posted; this needs operator attention.

2 similar comments
@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer could not complete review for #366 in AgentWorkforce/pear.
The review harness exited with code 1.
No review was posted; this needs operator attention.

@agent-relay-code

Copy link
Copy Markdown
Contributor

pr-reviewer could not complete review for #366 in AgentWorkforce/pear.
The review harness exited with code 1.
No review was posted; this needs operator attention.

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