Skip to content

feat(mount): --push-local-once teardown drain (stop dropping last-moment writeback drafts)#304

Merged
willwashburn merged 3 commits into
mainfrom
feat/push-local-once-drain
Jun 18, 2026
Merged

feat(mount): --push-local-once teardown drain (stop dropping last-moment writeback drafts)#304
willwashburn merged 3 commits into
mainfrom
feat/push-local-once-drain

Conversation

@willwashburn

@willwashburn willwashburn commented Jun 17, 2026

Copy link
Copy Markdown
Member

Root cause

Local writeback drafts are ingested into the durable outbox by the running daemon's sync cycle (watcher + pushLocal). A draft written after that cycle and just before shutdown — e.g. a one-shot deploy sandbox writing a final fire-and-forget Slack reply right before teardown — is on disk but not yet in the outbox.

The teardown cleanup runs --flush-outbox-once (FlushOutboxOnce: outbox-only, deliberately no local scan — the flush-124 cure), so that draft is never ingested and is silently dropped. Symptom downstream: the header message lands, the threaded reply vanishes.

Fix

Add PushLocalAndFlushOnce / --push-local-once: one pushLocal pass (scans the on-disk mirror — in the fresh cleanup process, so it catches drafts the running daemon never ingested) followed by an outbox flush, then exit. It skips pullRemote/digest/websocket, so it cannot reintroduce the pull-side flush-124 stalls. The only added cost over --flush-outbox-once is the local scan, so callers should invoke it only when pending local writes are detected and keep --flush-outbox-once for the no-pending-writes fast path (the cloud cleanup shell already does that find -newer detection — wired in the companion cloud PR).

Test

TestPushLocalAndFlushOnceIngestsUnsyncedLocalDraft: an unsynced on-disk draft is dropped by FlushOutboxOnce (0 uploads) but ingested + uploaded + drained by PushLocalAndFlushOnce (1 upload, empty outbox). Full mountsync suite + go vet pass.

Rollout

New relayfile-mount binary → release → cloud bumps RELAYFILE_MOUNT_VERSION + cleanup shell invokes --push-local-once on pending writes (companion PR) → snapshot rebuild. Backward-compatible: feature-detected; absent the flag, behavior is unchanged.

🤖 Generated with Claude Code

Review in cubic

…eback drafts aren't dropped

Local writeback drafts are ingested into the durable outbox by the running
daemon's sync cycle (watcher + pushLocal). A draft written after that cycle and
just before shutdown — e.g. a one-shot sandbox writing a final fire-and-forget
reply right before teardown — is on disk but not yet in the outbox. The teardown
cleanup runs --flush-outbox-once (outbox-only, deliberately no local scan, the
flush-124 cure), so that draft is never ingested and is silently dropped: the
header lands, the threaded reply vanishes.

Add PushLocalAndFlushOnce / --push-local-once: one pushLocal pass (scans the
on-disk mirror in a fresh process, so it catches drafts the running daemon never
ingested) + an outbox flush, then exit. It skips pullRemote/digest/websocket, so
it cannot reintroduce the pull-side flush-124 stalls — the only added cost is the
local scan, which callers should gate on "pending local writes detected" (keeping
--flush-outbox-once for the fast path).

Test: TestPushLocalAndFlushOnceIngestsUnsyncedLocalDraft — an unsynced on-disk
draft is dropped by FlushOutboxOnce (0 uploads) but ingested + uploaded + drained
by PushLocalAndFlushOnce. Full mountsync suite + vet pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <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 17, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

More reviews will be available in 38 minutes and 56 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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

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: eb37299c-2ca0-48c3-af1f-905ebf7652ce

📥 Commits

Reviewing files that changed from the base of the PR and between bceb8ae and 25c7c5f.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • internal/mountsync/syncer.go
📝 Walkthrough

Walkthrough

Adds a --push-local-once CLI flag to relayfile-mount and a new PushLocalAndFlushOnce method on Syncer. When the flag is set, the mount runner performs a single pass to ingest on-disk local writeback drafts, flush the durable outbox, validate full drain, and exit without entering the normal sync loop.

Changes

push-local-once drain path

Layer / File(s) Summary
PushLocalAndFlushOnce syncer method and test
internal/mountsync/syncer.go, internal/mountsync/syncer_test.go
Adds PushLocalAndFlushOnce: mutex-guarded, loads state, runs pushLocal to ingest on-disk drafts, flushes outbox records, errors if outbox still has NeedsAttention or Pending > 0, otherwise marks sync success and saves state. Regression test asserts FlushOutboxOnce skips the unsynced on-disk draft while PushLocalAndFlushOnce scans, ingests, uploads exactly once, and fully drains pending records.
CLI flag and runSinglePollingMount early-exit
cmd/relayfile-mount/main.go
Adds pushLocalOnce field to mountConfig, registers --push-local-once flag, wires the parsed value into config construction, and introduces an early-return branch in runSinglePollingMount that calls syncer.PushLocalAndFlushOnce under a timeout context and exits before the normal reconcile/sync loop.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • AgentWorkforce/relayfile#270: Directly modifies the same durable outbox flush path in internal/mountsync/syncer.go, changing how flush contexts and deadlines are derived for pending record uploads—directly adjacent to the flushOutboxRecords call added in PushLocalAndFlushOnce.

Poem

🐇 One push, one flush, then away I hop,
No loop, no watch, just a single stop.
The drafts on disk? I sniff them out,
Pack the outbox, drain with clout.
If pending lingers, I raise a cry—
Otherwise I save state and wave goodbye! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% 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 accurately describes the main feature added: a new --push-local-once teardown option that prevents loss of local writeback drafts before shutdown.
Description check ✅ Passed The description comprehensively explains the root cause, solution, testing approach, and rollout plan for the new --push-local-once feature.
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 feat/push-local-once-drain

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.

…(unblock CI)

The v0.9.6 release bumped packages/sdk/typescript/package.json optionalDependencies
to @relayfile/mount-*@0.9.6 but did not regenerate the package's standalone
package-lock.json, which still pinned 0.9.5. CI runs `npm ci` against that
lockfile, so it failed EUSAGE ("lock file's @relayfile/mount-*@0.9.5 does not
satisfy 0.9.6") on every PR (SDK Typecheck / contract / E2E / evals), #304
included. Pre-existing and unrelated to the daemon change in this PR (Go Build +
Go Test pass).

Regenerated with `npm install --no-workspaces --package-lock-only` (the package
is an npm workspace member but ships a standalone lockfile, so the workspace
root must be ignored to re-resolve its own optional deps). `npm ci` now validates
clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@willwashburn

Copy link
Copy Markdown
Member Author

CI red here was not this PR — it's pre-existing, repo-wide lockfile drift. npm ci (used by SDK Typecheck / contract / E2E / evals) failed EUSAGE: packages/sdk/typescript/package-lock.json still pinned @relayfile/mount-*@0.9.5 while package.json declares 0.9.6 (the v0.9.6 release didn't regenerate the SDK's standalone lockfile). Same failure on #303 and every open PR. The Go change here is fine — Go Build + Go Test pass.

Synced the lockfile to 0.9.6 (npm install --no-workspaces --package-lock-only — the package is a workspace member but ships a standalone lockfile, so the workspace root has to be ignored to re-resolve its own optional deps). npm ci validates clean now, so the npm-based checks should go green on re-run.

Heads up to whoever owns releases: the release tooling should regenerate packages/sdk/typescript/package-lock.json on version bumps, or this recurs on the next release (and it's currently blocking all relayfile PRs, not just this one).

@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

🤖 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 `@internal/mountsync/syncer.go`:
- Around line 1648-1655: The PushLocalAndFlushOnce function is missing the
mount-root invariant guard that protects operations on the local mirror. Add a
call to assertMountRootInvariant in PushLocalAndFlushOnce before invoking
s.pushLocal(ctx), following the same pattern used in syncReserved. This ensures
that teardown cannot bypass the clobber/missing-root recovery gate on the new
local-scan path opened by the pushLocal call.
🪄 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: 598e8d73-7410-48cf-a3fa-18c300a55ee5

📥 Commits

Reviewing files that changed from the base of the PR and between 2d5908c and bceb8ae.

⛔ Files ignored due to path filters (1)
  • packages/sdk/typescript/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • cmd/relayfile-mount/main.go
  • internal/mountsync/syncer.go
  • internal/mountsync/syncer_test.go

Comment thread internal/mountsync/syncer.go
…orkspace lockfile

Two follow-ups on #304:

- CodeRabbit (valid): PushLocalAndFlushOnce calls pushLocal (which scans+mutates
  the local mirror) without the mount-root invariant guard that syncReserved
  runs first. Add assertMountRootInvariant() at the top so the teardown drain
  can't operate on a wiped/clobbered mount root (recovery stays gated behind
  --reset-after-clobber). FlushOutboxOnce keeps skipping it — it's outbox-only.

- CI: the prior commit synced packages/sdk/typescript/package-lock.json, but CI
  runs `npm ci` in that workspace-member dir WITHOUT --no-workspaces, so it
  validates the ROOT package-lock.json — which still pinned @relayfile/mount-*@0.9.5
  vs package.json 0.9.6. Regenerated the root lockfile to 0.9.6; `npm ci`
  (workspace-aware) now validates clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@willwashburn

Copy link
Copy Markdown
Member Author

Addressed in 25c7c5f:

  • CodeRabbit (mount-root invariant): valid — added assertMountRootInvariant() at the top of PushLocalAndFlushOnce (matching syncReserved), so the teardown drain's pushLocal local-mirror scan can't run against a wiped/clobbered mount root. FlushOutboxOnce still skips it (outbox-only, never touches the mirror).
  • CI: the earlier lockfile sync fixed packages/sdk/typescript/package-lock.json, but CI runs npm ci in that workspace-member dir without --no-workspaces, so it actually validates the root package-lock.json — which still pinned @relayfile/mount-*@0.9.5. Regenerated the root lockfile to 0.9.6; npm ci (workspace-aware) validates clean locally now.

@github-actions

Copy link
Copy Markdown

Relayfile Eval Review

Run: .relayfile/evals/runs/2026-06-18T01-22-37-316Z-HEAD-provider
Mode: provider
Git SHA: c40bab6

Passed: 4 | Needs human: 0 | Reviewable: 0 | Missing output: 0 | Failed: 0 | Skipped: 0

Human Review Cases

No reviewable human-review cases captured Relayfile output.

@willwashburn willwashburn merged commit 7af7b47 into main Jun 18, 2026
9 of 10 checks passed
@willwashburn willwashburn deleted the feat/push-local-once-drain branch June 18, 2026 02:14
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