Skip to content

fix(source): skip unchanged pushes and give watch pushes the real budget - #154

Merged
mvanhorn merged 8 commits into
mainfrom
fix/source-push-cadence
Oct 15, 2026
Merged

mvanhorn merged 8 commits into
mainfrom
fix/source-push-cadence

Conversation

@mvanhorn

Copy link
Copy Markdown
Owner

Summary

An idle watch-mode source stops re-sending the same envelope to every sink every 30 seconds, and slow links stop failing at a hardcoded 60-second push budget. On the reporter's install, cookie-only pushes still cost about 1.8 MB per 30 s (about 5 GB a day) with nothing changing. On uplinks slower than about 12 Mbit/s, each push hit context deadline exceeded (6,356 times in one log), well under the 5-minute SyncClient timeout v1.1.0 set.

  • The source remembers, per sink (peer and URL), a hash of the last envelope that sink accepted. A sink that already holds the same payload is skipped, with a heartbeat at least every 10 minutes. Sequence and cookie access times are left out of the hash, so Chrome bumping last_access_utc on open tabs does not count as a change. The sent payload still carries the access times.
  • Only a successful POST counts as delivered. A failed POST forgets that sink's last delivery, so it is always retried and never skipped on an older acknowledgement.
  • Watch-mode pushes get the same budget as --once (SyncClient timeout plus 30 s). The watcher skips a trigger while a push is still running, so slow pushes cannot pile up; the next 30 s tick picks up anything skipped.
  • A Linux sink with live CDP on now re-injects the cookies from its last /sync into the running Chrome every 30 s, locally, applying its current cookie policy. The source's 30 s re-push used to do this as a side effect, covering browser contexts an agent opened since the last sync and a Chrome that restarted. Without it, dedup would leave those logged out until the heartbeat. Re-injects are serialized with /sync, so a newer set always lands last.

Reported by @Dannyxlm in #149 (items 2 and 3).

Related: #149

Verified

  • go build ./...
  • go vet ./...
  • go test -race ./...
  • golangci-lint v2.11.4 (the CI pin): 0 issues

Dedup tests drive the real fan-out on a stub clock:

  • two identical pushes inside 10 minutes make one POST, and one past 10 minutes POSTs again
  • a changed cookie POSTs at once, while a change only to last_access_utc does not
  • a 500 is re-sent, and a payload that follows a failed POST is not skipped on an earlier acknowledgement
  • with two sinks where one failed, only the failed sink is re-sent
  • Sequence strictly increases across real POSTs
  • a slow POST does not bring the heartbeat forward

Watcher tests check that the push deadline is the configured budget and that only one push runs at a time. Re-injector tests cover re-inject after /sync and nothing before it, no ticker when live CDP is off or on --dry-run, failure and recovery each logged once, the current policy applied each tick, and a newer /sync landing after an in-flight tick.

Code review: ce-code-review at full depth, with correctness, reliability, testing and maintainability reviewers plus an independent Codex adversarial read (gpt-6-luna, independence verified).

  • Verdict: Ready with fixes.
  • All six actionable findings are fixed on this branch: the tick//sync race, forgetting a delivery after a failed POST, timestamping deliveries per sink, the shared endpoint helper, splitting the new code into source_dedup.go and sink_reinject.go, and a dry-run guard test.

Notes


Compound Engineering

🤖 Generated with Claude Code

https://claude.ai/code/session_01LgVWtkXgDGWMh6L95d8piN

mvanhorn and others added 5 commits October 15, 2026 00:01
A watch-mode source re-sent the full envelope to every sink on each 30s
baseline tick, even when nothing had changed, and each watch-mode push
was capped at 60s, below the 5-minute SyncClient timeout, so slow links
failed with context deadline exceeded.

The source now remembers, per sink, the hash of the last envelope it
delivered (Sequence and cookie access times excluded) and skips a sink
that already holds the same payload, re-sending at least every 10
minutes as a heartbeat. Only a successful POST counts as delivered, so a
failed sink is always retried. Watch-mode pushes get the same budget as
--once (SyncClient timeout plus 30s), and the watcher skips a trigger
while a push is still running so slow pushes cannot pile up.

Reported by @Dannyxlm in #149.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LgVWtkXgDGWMh6L95d8piN
The source's 30s re-push used to re-inject cookies into a Linux sink's
live Chrome as a side effect, which covered browser contexts opened
since the last sync and a Chrome that restarted. Now that unchanged
pushes are skipped, the sink holds the cookies from its last /sync and
re-injects them locally every 30s, applying its current cookie policy.
Failures log once and recovery logs once.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LgVWtkXgDGWMh6L95d8piN
- Forget a sink's remembered delivery when a POST is sent and fails, so
  a later payload is never skipped on an older acknowledgement.
- Stamp each delivery when its POST succeeds, so a slow POST does not
  bring the next heartbeat forward.
- Serialize the live CDP re-inject tick with /sync applies so a newer
  /sync set always lands after an in-flight tick.
- Use liveCDPEndpointFor everywhere, and move the re-injector and the
  dedup log into sink_reinject.go and source_dedup.go.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LgVWtkXgDGWMh6L95d8piN
@greptile-apps

greptile-apps Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no new blocking issue was found.

Summary

The source skips unchanged deliveries per sink until a 10-minute heartbeat and gives watch pushes the same budget as one-shot pushes.

  • Sources skip unchanged payloads and send a heartbeat every 10 minutes.
  • Watch pushes get the source --once time budget without piling up.
  • Live-CDP sinks re-inject their last synced cookies into Chrome every 30 seconds.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Source trigger] --> B{Push already running?}
  B -->|Yes| C[Skip trigger]
  B -->|No| D[Read current payload]
  D --> E{Sink has same payload within heartbeat?}
  E -->|Yes| F[Skip POST]
  E -->|No| G[POST to sink]
  G -->|Success| H[Remember delivery and time]
  G -->|Failure| I[Forget delivery]
  H --> J[Hold accepted cookies at sink]
  J --> K[Every 30 seconds: check current policy]
  K --> L[Inject into live Chrome]
Loading

Reviews (4) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." · Reviewed by Greptile

Comment thread internal/cli/sink_reinject.go Resolved
Comment thread internal/cli/sink_reinject.go Resolved
mvanhorn and others added 3 commits October 15, 2026 00:33
…status

Addresses review feedback on #154. An older /sync that finishes after a
newer one from the same source no longer replaces the held cookie set,
and each re-inject tick that attempts an inject records its outcome in
the sink state, so status reflects the latest live CDP result.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LgVWtkXgDGWMh6L95d8piN
# Conflicts:
#	CHANGELOG.md
#	docs/faq.md
#	internal/cli/source.go
#	internal/cli/source_test.go
@mvanhorn
mvanhorn merged commit 70d0657 into main Oct 15, 2026
6 checks passed
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