# Working from a checkout (https://lazyfrog.dev/docs/guide/working-from-a-checkout)

{/* Generated by scripts/generate-docs.ts from services/gateway/src/guide.md. Do not edit. */}

When the user works on a copy of the code with Claude Code or Codex (on their own machine or in
their AI's cloud session), the preview stays the source of truth: it is what runs, what other
people's AIs work on and what a publish takes. Work in the checkout; push to update the preview;
use the LazyFrog tools to look (logs, database, screenshots) and to publish. The preview and the
repository are one history.

- **The code and the tools.** `git clone https://git.lazyfrog.dev/<project>.git`: git asks for a
  user name (anything) and a password, a personal token the user makes in the portal (Account ›
  Access tokens, "Read and write" to push; you can't make one, and only the project's owner and
  editors can push). New projects carry `.mcp.json`, which Claude Code reads (it asks once, then
  connects LazyFrog's tools), and `CLAUDE.md` and `AGENTS.md` with these rules in short. Codex:
  `codex mcp add lazyfrog --url https://mcp.lazyfrog.dev/mcp`, then `codex mcp login lazyfrog`.
  An older project may not have the files: offer to add them (`.mcp.json` is
  `{"mcpServers": {"lazyfrog": {"type": "http", "url": "https://mcp.lazyfrog.dev/mcp"}}}`).
- **The remote.** In a clone from git.lazyfrog.dev it is `origin`; in an export, `origin` or
  `lazyfrog` (when `origin` is GitHub). A checkout of the GitHub repository adds it once:
  `git remote add lazyfrog https://git.lazyfrog.dev/<project>.git`. Below, `lazyfrog` means
  whichever names it.
- **Code that isn't on LazyFrog yet** (a repository only on the user's machine; one on GitHub is
  `project import` with its address instead): `project create` from the template nearest its
  stack (`blank` for most), then in the checkout
  `git remote add lazyfrog https://git.lazyfrog.dev/<project>.git`, `git fetch lazyfrog` and
  `git merge lazyfrog/main --allow-unrelated-histories`. The project's starter files (`services.json`, `Dockerfile`,
  `compose.yml`, `migrations/0001_init.sql`, `CLAUDE.md`, `.mcp.json`…) join the user's history.
  Where both have a file, keep the user's (`public/index.html`, `README.md`) and add LazyFrog's
  `.gitignore` lines to theirs. Then make it run here before pushing: point `services.json` at the
  app (`dev`, `build`, `start`, listening on `$PORT`, `healthcheck_path`), rewrite `Dockerfile` and
  `compose.yml` for its stack, and move its schema into `migrations/`, numbered after
  `0001_init.sql`; an app with its own migration tool sets it as `"migrate"` instead (one or the
  other, never both). Commit, and push.
- **Push to update the preview.** Edit and try it in the checkout as anywhere else (`compose.yml`
  runs the app with a PostgreSQL of its own and the same `DATABASE_URL`; `.env.example` names the
  variables), commit, then `git push lazyfrog main` (the preview's branch). The push answers on
  git's progress lines (`remote: LazyFrog: …`): "preview updated: https://p-….lazyfrog.app ·
  build 41 s · 2 migrations applied to preview", or the step that failed with its last lines and
  what to do. Read them; don't push again unchanged. When the start takes longer than about a
  minute and a half, the push ends with "still …; the outcome goes on the project's activity":
  follow with `service_status`.
- **When the push is refused**, it says why in its last line (`! [remote rejected] main -> main
  (…)`), and nothing in the preview changed:
  - "the preview has N commits you don't have": someone (or an AI over the tools) changed the
    preview; uncommitted work there was committed first, as "Changes made in the LazyFrog
    workspace". `git pull lazyfrog main`, resolve any conflicts, push again. Never force: a forced
    or non-fast-forward push is always refused.
  - "contains what looks like a secret": the file and line are above it. Take the value out of
    those commits (amend or rebase), put it in a secret (`secrets request`), and push again.
  - "another AI is working in this project": its lease (one AI at a time) is held; try again in a
    minute.
  - "over the plan's disk", or a read-only token: say so to the user (the plan, or a "Read and
    write" token).
- **Other branches** are stored ("stored; the preview follows main"); only the preview's branch
  changes the preview.
- **Schema changes**: add `migrations/000N_name.sql` in the checkout and push: pending migrations
  run on the preview's database as part of the push (a snapshot first), unless `services.json`
  says `"migrate_on_push": false` (then `db migrate` through the tools). A publish applies them to
  the live database after its rehearsal.
- **Without the push** (or to change only a file or two), the tools still work: `fs_write` `files`
  (paths `workspace/…`), `git` `commit`, then `git pull` in the checkout.
- **Look through the tools**: `logs_tail`, `service_status`, `http_get`, `preview_screenshot`, and
  `db_read` and `db_schema` for the preview's database (it isn't reachable from the machine).
- **Never commit** secrets (`.env`, keys, tokens: ask the owner with `secrets request`, and keep
  local values in an ignored `.env`), `.lazyfrog/`, dependencies or build output.
- **Publishing** goes through the tools: `deploy preflight`, then `deploy publish` when the user
  says so (see Publishing). A push publishes by itself only when the user turned `auto_publish` on.
