> ## Documentation Index
> Fetch the complete documentation index at: https://docs.comforthub.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Run the sweep and the reconciler

> Read the nightly drift findings, run the sweep by hand, and see what the reconciler would write before letting it write

export const Touches = ({systems}) => <p>
    <strong>Systems involved:</strong>{" "}
    {systems.split(",").map((s, i) => <span key={s}>
        {i > 0 ? " · " : ""}
        <a href={"/systems/" + s.trim()}>{s.trim().replace(/-/g, " ")}</a>
      </span>)}
  </p>;

<Touches systems="zoho-crm, supabase-backend" />

The sweep compares every synced record on both sides and keeps a list of findings: one row per difference, opened when first seen and closed when the two sides agree again. The reconciler reads the open findings and copies Zoho's value over ours for the fields Zoho owns. It prints what it would write and writes nothing unless a person says so. The sweep's schedule and its ceiling are on [Scheduled jobs](/reference/scheduled-jobs).

## Before you start

* Access to the backend repo's Actions page on GitHub. Both jobs run there, never from a laptop.
* The SQL editor for the engine, to read the findings.
* Nothing else touching the same records: a sweep that runs while a bulk job is half done records the half as drift.

## Steps

<Steps>
  <Step title="Read the findings">
    In the SQL editor, list the open findings grouped by kind and module. Each row names the record on both sides and, for a field difference, the field with our value and Zoho's. The kinds, and which fix goes with each, are on [Fix a record that did not sync](/handbook/running/fix-a-record-that-did-not-sync).
  </Step>

  <Step title="Run the sweep by hand if you cannot wait for the night">
    On the Actions page, open the **sweep** workflow and click **Run workflow**. Leave the ceiling at its default; the number is on [Scheduled jobs](/reference/scheduled-jobs). The job goes red when the open findings exceed the ceiling; the findings are still written in full, and the red is the gauge telling you the pile is too big. The sweep and the crosswalk bootstrap share one writer lock, so they never run at the same time: a run started while the other is going waits its turn.
  </Step>

  <Step title="Run the reconciler dry">
    Open the **reconcile** workflow and click **Run workflow**. Its four inputs, in words: the mode, **dry-run** or **write**; a module name to limit the run, or blank for every module; the most writes it may make before it refuses to run at all; and a box that prints sample values, which may include customer data, so leave it off unless you need it. Dry, it prints per module how many records it would write and which fields, then "dry run — nothing written". Fields it measured but never writes are listed separately with the reason: a link, or a field we own. Read this list before anything else.
  </Step>

  <Step title="Decide, then write only if you mean it">
    The write mode is the owner's decision, not a routine. It copies Zoho's value over ours for every field in the write set, marks the pair as synced so the echo is recognised, closes the finding with the reason "reconciled from Zoho", and records the run on the integration ledger, flagged for a person to read. Run it with a module name to limit the blast, and a ceiling below the write set's size to have it refuse rather than surprise you.
  </Step>

  <Step title="Confirm the finding closed">
    After the record is fixed, on either side, the next sweep closes its finding with the reason "not re-seen by sweep". Nothing is deleted; a closed finding keeps its history.
  </Step>
</Steps>

## Sending a back catalogue

When a module is switched on with rows here that Zoho has never seen, the **backfill-outbound** workflow queues their first push. **list** counts them; **write** queues them, up to the number in the max box. Go one, then ten, then a hundred, then the rest, and read the outcome between runs: a refusal that hits one row hits every row shaped like it.

## How you know it worked

* A finding you opened on purpose appears in the list after a sweep, naming the record, the field, our value and Zoho's.
* A dry run of the reconciler lists that record under its module with the field, and writes nothing.
* After the record is put right on either side, the next sweep closes the finding, and the Zoho record was never touched by the sweep.

## If it did not work

* **The sweep job is red with "GATE" in its log.** The open findings exceed the ceiling. The findings were written; clean up the biggest group and the next run goes green on its own.
* **The reconciler says "skipped: no Zoho record returned".** The record is gone from Zoho since the sweep saw it; the next sweep reclassifies the finding.
* **A module is "measured only".** Invoices, payments, subscriptions and subscription payments are never reconciled from Zoho; money is settled by hand. See [Sync rules by module](/reference/sync-rules-by-module).
* **The finding is a link difference.** The reconciler never repoints a link. Fix the link in Zoho; the next sweep closes it.
* **A module shows zero records on both sides.** The sweep did measure it; there was nothing to measure. A module the sweep leaves out is listed under "excluded" in the run's log with the reason; a zero pair under "observations" is an empty module, not a skipped one.

## Related

* [How sync works](/handbook/running/how-sync-works)
* [Fix a record that did not sync](/handbook/running/fix-a-record-that-did-not-sync)
* [Scheduled jobs](/reference/scheduled-jobs)
