Working with your app in git
Connect an app to a GitHub repository and Stacker keeps it in sync both ways. Every change made in Stacker — by you, by your team, or by an AI agent — lands as a commit in the repository. Commits you push to the repository are validated and applied to the live app.
Available on request
Git remotes are enabled per workspace. If you don't see a GitHub card at the top of your app's History tab, ask us to switch it on for your workspace.
What's in the repository
The repository holds the definition of your app — everything that describes how it works — laid out as ordinary files so diffs and reviews read naturally.
spec/app.json name, icon, description
spec/tables/<slug>.json one file per table (fields, types, options)
spec/views/<slug>.json list views
spec/layouts/<table>.json record detail layouts
spec/pages/<slug>.json pages
spec/permissions.json roles
spec/navigation.json sidebar
spec/workflows/<key>.json workflow settings (trigger, schedule)
workflows/<key>.code.ts workflow code, as a real source file
spec/agents/<key>.json agent settings
agents/<key>.instructions.md agent instructions, as markdown
spec/portals/<key>.json portal settings
portals/<key>/... portal source (pages, components, styles)The commit history is the same one you see in the History tab. Each edit, template update, restore and checkpoint is a commit with a message explaining what changed and who made it. Backups appear as tags.
Your records are never in the repository. Customer data stays in Stacker; only the app's structure and code travel through git. Secrets (connector credentials, API keys) are never exported either.
Connecting a repository
- Open your app's History tab. The GitHub card is at the top.
- Click Install the Stacker GitHub App. GitHub asks which organization to install it on; pick the organization that should own the repositories. You are sent straight back to Stacker afterwards. This is done once per workspace.
- Back on the card, either create a new private repository (Stacker names it after the app; you can change the name) or link an existing repository (Link an empty repository). A linked repository must be empty — no commits yet — so that Stacker's first push can seed it.
- Within a minute the app's full history is pushed and the card shows In sync, the clone URL, and the last push time.
GitHub Apps can only create repositories inside organizations, not on personal accounts. To use a personal account, create the empty repository yourself on GitHub and link it.
Stacker → GitHub: automatic pushes
Every change to the app definition becomes a commit within a few seconds and is pushed shortly after (pushes are batched over about 30 seconds so a burst of edits from an agent arrives as a few commits, not a flood of pushes).
Stacker never force-pushes. If the repository has a commit Stacker does not know about, the push is held, the card shows Rebase needed or Push rejected, and Stacker first tries to import your commits (below). Once the repository and the app agree again, pushes resume on their own.
Push now on the card sends any pending commits immediately; Check repository asks Stacker to look for commits you pushed, in case a notification from GitHub was missed.
GitHub → Stacker: what happens when you push
Clone the repository, make changes in your editor, commit, and push to the default branch (main). Stacker is notified by GitHub and runs a check on the pushed commit, visible on the commit in GitHub as a check run and on the card.
git clone https://github.com/<org>/<repo>.git
cd <repo>
# edit portals/customer/pages/index.tsx, workflows/welcome.code.ts, spec/tables/deals.json ...
git commit -am "Tighten the deals list filter"
git pushWhen the check passes
- • The live app is updated to match the pushed tree.
- • Your commits appear in the History tab as Pushed from git, with your name and message exactly as you wrote them.
- • Portals reload with the new code; schema changes (new tables, fields, views) are applied.
- • The card shows In sync again and Stacker's own pushes resume.
When the check fails
- • Nothing is applied to the app — it keeps running as before.
- • The check run and the card say why (see the list below).
- • Fix the branch (amend or add a commit), push again, and the check reruns.
- • Until then Stacker holds its own pushes so nothing is overwritten.
What the check enforces
Builds on the app's history
Your branch must start from the commit Stacker last pushed. Pushes from a stale clone that fork the history are refused with Rebase needed — see below.
No merge commits
Stacker's history is a straight line. Rebase your branch instead of merging; `git pull --rebase` is the habit that keeps this painless.
Only known files
Every file must fit the layout above. A stray file (a README, an editor config, a build artefact) is refused with the path named — delete it or move it out of the repository.
Valid definitions
spec/*.json files must parse and match their schemas: a field type that doesn't exist or a page that references a missing table is refused, with the path and the problem.
No deleted tables or fields
A push may add and change tables and fields, but never remove them — a removed field would destroy customer data. Delete tables and fields in Stacker, where the impact is shown and confirmed.
Portal code compiles
The pushed portal source is type-checked and smoke-rendered before it goes live. A type error or a page that crashes on load is refused with the compiler output.
Reproducible commits
Stacker stores your commits byte-for-byte so the repository and the app never drift. Executable bits, symlinks and submodules cannot be represented and are refused.
“Rebase needed”: when both sides changed
If someone edited the app in Stacker after you cloned, and you push a commit built on the older state, the two histories have forked. Stacker will not pick a winner. Instead it publishes its own current state as the branch stacker in your repository and marks the card Rebase needed. Replay your work on top of it:
git fetch origin
git rebase origin/stacker # resolve any conflicts in your editor, then:
git push --force-with-leaseThe rebase rewrites the commits GitHub already holds on main (the ones Stacker refused), so GitHub rejects a plain git push. --force-with-lease replaces exactly those commits and still stops if the branch moved again underneath you. After the push passes its check, main is a straight line again that contains both the Stacker edits and yours, and the card returns to In sync. The stacker branch is left behind as a marker; you can delete it.
Only ever rewrite commits Stacker has not imported. Force-pushing over history that Stacker has already recorded is refused as unrelated history, and you will need to rebase onto origin/stacker anyway.
Playing well with agents and template updates
Agent edits and your pushed commits interleave in one history. Agents can read that history — ask “what changed in the deals page this week and why?” and the answer includes your commits, by your name.
Template updates are commits like any other, so they show up in the repository with the release notes in the message. Your pushed changes count as customizations: the next template update merges around them, and where the template and your commit touched the same lines the conflict review shows both sides.
Restoring a backup or rolling back a template update creates a new commit that puts the old content back — history is never rewritten, so your clone only ever needs git pull --rebase.
Disconnecting
Disconnect on the card stops syncing in both directions. The repository is left exactly as it is — Stacker never deletes repositories or commits — and the app keeps its full history inside Stacker. You can connect a different repository afterwards, as long as it is empty.