# GitHub sync and conflicts

> Clone with `ok clone`, enable auto-sync, push/pull via CLI, and resolve merge conflicts through MCP `conflicts` and `resolve_conflict` tools.

- Repository: sashimikun/open-knowledge
- GitHub: https://github.com/sashimikun/open-knowledge
- Human docs: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e
- Complete Markdown: https://grok-wiki.com/public/docs/sashimikun-open-knowledge-5c45105c876e/llms-full.txt

## Source Files

- `docs/content/features/github-sync.mdx`
- `packages/cli/src/commands/clone.ts`
- `packages/cli/src/commands/sync.ts`
- `packages/cli/src/commands/push.ts`
- `packages/cli/src/commands/pull.ts`
- `packages/server/src/mcp/tools/conflicts.ts`
- `packages/server/src/mcp/tools/resolve-conflict.ts`

---

---
title: "GitHub sync and conflicts"
description: "Clone with `ok clone`, enable auto-sync, push/pull via CLI, and resolve merge conflicts through MCP `conflicts` and `resolve_conflict` tools."
---

Open Knowledge keeps project markdown in a git working tree and, when a GitHub `origin` remote is present, runs a background sync engine inside the Hocuspocus collaboration server. The engine fetches remote commits, commits local edits, and pushes them back on a schedule when auto-sync is enabled. The `ok` CLI exposes `clone`, `sync`, `push`, and `pull`; when a project server is running, those commands route through `POST /api/sync/trigger` instead of calling `git` directly. Merge conflicts are tracked in `.ok/local/conflicts.json`, surfaced in the editor, and resolvable through the unified diff UI or the MCP `conflicts` and `resolve_conflict` tools.

<Warning>
Only enable auto-sync on repositories where you accept Open Knowledge writing commits to remote history. Automated sync can clutter or reshape Git history on active branches.
</Warning>

## Clone with `ok clone`

`ok clone` clones a repository, scaffolds Open Knowledge if needed, excludes `.ok/` from git tracking, and opens the editor.

```bash
ok clone owner/repo
ok clone https://github.com/owner/repo.git
ok clone owner/repo ./my-folder -b feature-branch
```

| Flag / argument | Behavior |
| --- | --- |
| `<url>` | Full HTTPS/SSH URL or `owner/repo` shorthand (shorthand resolves to `https://github.com/owner/repo`) |
| `[dir]` | Target directory; defaults to `./<repo-name>`. Must not exist or must be empty |
| `-b, --branch <branch>` | Check out a branch; if upstream branch is missing, falls back to the default branch |
| `--json` | Emit JSONL progress events (`progress`, `complete`, `error`) instead of stderr UI |

**Authentication.** Public `https://github.com` repositories clone without credentials. Private repos require a stored token (`ok auth login`) or credentials from the `gh` CLI. On auth failure the CLI prints actionable recovery steps (`ok auth login` or `ok auth pat` with `repo` scope).

**Post-clone steps.** After a successful clone, `ok clone` runs `ok init` (without MCP registration), appends `.ok/` to the repo's git exclude file when possible, and—in non-JSON mode—`chdir`s into the clone and runs `ok start`.

<Note>
JSON mode (`--json`) prints machine-readable events only; it does not auto-start the server or open the editor.
</Note>

## Enable auto-sync

Auto-sync is controlled by two config keys with different scopes:

| Key | Scope | File | Values |
| --- | --- | --- | --- |
| `autoSync.enabled` | project-local | `.ok/local/config.yml` | `true`, `false`, or `null` (not chosen) |
| `autoSync.default` | project | `.ok/config.yml` | `true`, `false`, or `null` (ask on first open) |

Precedence: a per-machine `autoSync.enabled` choice overrides the committed `autoSync.default`. When `autoSync.enabled` is `null` and no local choice exists, the engine reads `autoSync.default` from project config; `null` triggers the editor onboarding prompt on first remote-detected open.

While sync is active the engine:

- Fetches and pulls remote commits on an interval
- Commits local content edits with the configured Git identity
- Pushes commits so collaborators see changes

Toggle sync from the editor sync indicator, **Settings → Sync**, or by writing `autoSync.enabled` into `.ok/local/config.yml`. Set a team default with `autoSync.default` in committed `.ok/config.yml` under **Sync → Shared default**.

<Callout type="warn">
Pulls can overwrite uncommitted local file changes. Commit or discard work-in-progress before enabling sync on a dirty tree.
</Callout>

## Manual sync via CLI

Three CLI commands share one implementation (`runSync`):

| Command | `op` sent to server | Direct-git fallback (no server) |
| --- | --- | --- |
| `ok sync` | `sync` | `git pull`, then `git push` |
| `ok pull` | `pull` | `git pull` only |
| `ok push` | `push` | `git push` only |

When `.ok/local/server.lock` records a running server port, the CLI posts to `http://127.0.0.1:<port>/api/sync/trigger` with body `{ "op": "sync" \| "pull" \| "push" }` and returns `202` on acceptance. If the trigger fails or no server is running, the CLI falls back to direct `simple-git` operations in the current project directory.

```bash
ok sync          # pull + push (server: push cycle then pull cycle)
ok pull --json   # JSONL: step, pull, complete events
ok push
```

Add `--json` on any command for JSONL telemetry (`triggered`, `step`, `pull`, `push`, `complete`, `error`).

<Info>
Server-triggered `sync` runs push before pull; the direct-git fallback runs pull before push. Both paths reconcile local and remote history but ordering differs.
</Info>

## Sync status and engine states

`GET /api/sync/status` (also polled by the editor sync indicator) returns:

| Field | Meaning |
| --- | --- |
| `state` | Current phase: `dormant`, `idle`, `fetching`, `pulling`, `pushing`, `conflict`, `offline`, `auth-error`, or `disabled` |
| `ahead` / `behind` | Commits relative to tracked remote branch |
| `conflictCount` | Unresolved merge conflicts |
| `syncEnabled` | Whether auto-sync is on for this machine |
| `identityUnresolved` | Git user.name/email not configured |
| `pausedReason` | Why sync paused (e.g. dirty tree, external changes pending) |
| `pushPermission` | Whether the signed-in account can push |

The indicator also exposes last sync time, manual sync trigger, and auth-error recovery entry points.

```mermaid
stateDiagram-v2
  [*] --> dormant: no remote
  dormant --> disabled: sync off
  disabled --> idle: sync enabled
  idle --> fetching: scheduled fetch
  fetching --> pulling: behind remote
  fetching --> pushing: ahead local
  pulling --> conflict: merge conflict
  pushing --> idle: success
  pulling --> idle: success
  conflict --> idle: all conflicts resolved
  idle --> auth-error: token invalid
  idle --> offline: network failure
  auth-error --> idle: reconnected
  offline --> idle: network restored
```

## When conflicts block work

During a merge conflict Open Knowledge:

- Pins a **Conflicts** section at the top of the file tree
- Badges conflicted tabs with `⚠`
- Swaps the editor to a unified diff view for human resolution

Conflicted documents receive `lifecycle.status === "conflict"`. All mutating MCP tools (`write`, `edit`, `delete`, `move`, `restore_version`, and agent undo) refuse with HTTP `409` / `DocInConflictError` until the conflict is resolved.

Tracked conflicts persist in `.ok/local/conflicts.json` (version 1 schema: `branch`, `conflicts[]` with `file`, `detectedAt`, optional stage SHAs).

## Resolve conflicts in the editor

<Steps>
<Step title="Open the conflict">
Click the badged tab or a row in the **Conflicts** section. The editor shows base, yours, and theirs in a unified diff.
</Step>
<Step title="Choose a side or merge">
Pick **Mine**, **Theirs**, or edit merged content in the diff view. Delete-vs-modify shapes offer deletion-specific choices.
</Step>
<Step title="Verify sync resumes">
When every conflict is cleared the engine creates a merge commit (`git commit --no-edit`). The sync indicator returns to `idle` and MCP writes succeed again.
</Step>
</Steps>

## Resolve conflicts via MCP

Both tools require the Hocuspocus server (`ok start`). They proxy to HTTP routes under `/api/sync/*`.

### `conflicts`

<ParamField body="kind" type='"list" | "content"' required>
`list` enumerates tracked conflicts. `content` fetches merge stages for one file.
</ParamField>

<ParamField body="file" type="string">
Required when `kind` is `"content"`. Relative path **with** `.md` or `.mdx` extension (e.g. `notes/sso.md`). Git stages key on the exact path.
</ParamField>

<ParamField body="cwd" type="string">
Optional project directory override for multi-root agents.
</ParamField>

**`kind: "list"`** returns `{ list: [{ file, detectedAt, ... }] }`. Empty list means no tracked conflicts.

**`kind: "content"`** returns `{ content: { file, base, ours, theirs, shape, lifecycleStatus } }`:

| `shape` | Meaning | Typical `resolve_conflict` strategy |
| --- | --- | --- |
| `both-modified` | Both sides edited content | `mine`, `theirs`, or `content` |
| `delete-modify` | You deleted; they edited (`ours` empty) | `delete` to keep deletion, or `theirs` to restore their version |
| `modify-delete` | You edited; they deleted (`theirs` empty) | `delete` to accept their deletion, or `mine` to keep your version |

The MCP tool requests `source=ytext` so `ours` reflects live Y.Text when the document is loaded server-side (what the human sees in the editor).

<RequestExample>
```json
conflicts({ "kind": "list" })
```
</RequestExample>

<RequestExample>
```json
conflicts({ "kind": "content", "file": "notes/sso.md" })
```
</RequestExample>

### `resolve_conflict`

<Warning>
Destructive: modifies the working tree and may create a git commit when all conflicts are cleared.
</Warning>

<ParamField body="file" type="string" required>
Relative path with extension (e.g. `notes/sso.md`).
</ParamField>

<ParamField body="strategy" type='"mine" | "theirs" | "content" | "delete"' required>
Resolution strategy. Inspect `shape` from `conflicts({ kind: "content" })` before choosing.
</ParamField>

<ParamField body="content" type="string">
Required and non-empty when `strategy` is `"content"`. Ignored otherwise.
</ParamField>

| Strategy | Git operations |
| --- | --- |
| `mine` | `git checkout --ours -- <file>` then `git add` |
| `theirs` | `git checkout --theirs -- <file>` then `git add` |
| `content` | Write exact bytes to disk, then `git add` |
| `delete` | `git rm <file>` |

When the last tracked conflict is resolved, `ConflictStore` runs `git commit --no-edit` to finish the merge. On commit failure the store may re-add unmerged files; re-call `conflicts({ kind: "list" })` to confirm post-state.

<RequestExample>
```json
resolve_conflict({ "file": "notes/sso.md", "strategy": "theirs" })
```
</RequestExample>

<RequestExample>
```json
resolve_conflict({
  "file": "notes/sso.md",
  "strategy": "content",
  "content": "# Merged title\n\nCombined body text."
})
```
</RequestExample>

```mermaid
sequenceDiagram
  participant Agent
  participant MCP as MCP server
  participant API as /api/sync/*
  participant Store as ConflictStore
  participant Git

  Agent->>MCP: conflicts({ kind: "list" })
  MCP->>API: GET /api/sync/conflicts
  API-->>Agent: list of conflicted files

  Agent->>MCP: conflicts({ kind: "content", file })
  MCP->>API: GET /api/sync/conflict-content?source=ytext
  API-->>Agent: base, ours, theirs, shape

  Agent->>MCP: resolve_conflict({ file, strategy })
  MCP->>API: POST /api/sync/resolve-conflict
  API->>Store: resolveConflict()
  Store->>Git: checkout/add/rm + commit
  API-->>Agent: { ok: true, file }
```

Proactively detect conflicts with `conflicts({ kind: "list" })` or `exec("cat …")`, which includes `lifecycle` on document reads.

## Common failure modes

| Symptom | Cause | Recovery |
| --- | --- | --- |
| Sync paused, `conflictCount > 0` | Unresolved merge conflict | Resolve in editor or via `resolve_conflict` |
| `pausedReason: dirty-tree` | Uncommitted local edits would be overwritten | Commit or discard changes, then trigger sync |
| `state: auth-error` | Expired or revoked GitHub token | `ok auth login` or reconnect from sync indicator |
| Push toggle disabled | Account lacks write access | Request collaborator access; sync preference stays preserved in memory |
| Sync auto-disabled | Protected branch rejected push | Push to an unprotected branch or use a PR workflow |
| `identityUnresolved: true` | Missing `user.name` / `user.email` | Set Git identity when prompted |
| `ok clone` auth failure | Missing or insufficient token scopes | `ok auth login` or `ok auth pat` with `repo` scope |
| Diverged history after force-push | Remote rewritten | Coordinate with team; consider timeline recovery |
| Detached HEAD | Checked out specific commit | Return to a branch (`main`, etc.) |

Network blips set `state` to `offline`; the engine retries automatically. Trigger a manual sync from the indicator if a pause persists.

## HTTP routes (server-backed)

| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/api/sync/status` | Engine state, ahead/behind, `conflictCount` |
| `POST` | `/api/sync/trigger` | Body `{ op?: "sync" \| "pull" \| "push" }` — returns `202` |
| `GET` | `/api/sync/conflicts` | List tracked conflicts |
| `GET` | `/api/sync/conflict-content?file=&source=ytext` | Merge stages for one file |
| `POST` | `/api/sync/resolve-conflict` | Body `{ file, strategy, content? }` |

CLI `ok sync` / `ok pull` / `ok push` and MCP conflict tools all depend on these routes when the collaboration server is running.

## Related pages

<CardGroup>
<Card title="Auth reference" href="/auth-reference">
GitHub OAuth device flow, PAT storage, `gh` CLI credential reuse, and token scopes for clone and sync.
</Card>
<Card title="CLI reference" href="/cli-reference">
All `ok` subcommands, global flags, and server management commands.
</Card>
<Card title="MCP tools reference" href="/mcp-tools-reference">
Full catalog of seventeen MCP tools, conflict guards, and preview envelopes.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`autoSync.enabled`, `autoSync.default`, and config precedence across scopes.
</Card>
<Card title="Team sharing" href="/team-sharing">
Publish projects to GitHub and construct share URLs with `ok share`.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Diagnose server lock state, repair stale configs, and recover from sync failures.
</Card>
</CardGroup>
