GitHub & source control
Factorial Code stores your code in the cloud, and the workspace console is a full editor — you never
have to use Git. But a team that wants pull requests, code review, branch history or CI can have
all of it: fcode team:clone lays every App and global
workspace the team owns out on disk in one predictable structure, which is exactly what a repository
needs.
Get the whole team on disk
Section titled “Get the whole team on disk”Install the CLI and clone your development team:
$ pnpm install -g @factorialco/fcode-cli$ fcode login$ fcode team:cloneYou get one folder per App, each holding a checkout of that App’s development workspace, and the
global workspaces the team owns under global-workspaces/:
📦 acme-payroll┣ 📂 .fcode┃ ┗ 📜 team.json # the team, the Apps and the global workspaces cloned into it┣ 📂 .claude┃ ┗ 📂 skills # agent skills, installed once for the whole team┣ 📂 global-workspaces┃ ┗ 📂 acme-base # a global workspace the team owns: processes, modules, variables…┣ 📂 payroll-sync┃ ┣ 📜 settings.json # the App — name, description, id┃ ┗ 📂 app # the App's workspace: processes, modules, variables…┗ 📂 time-off-sync ┗ ...Create the repository
Section titled “Create the repository”One repository for the whole team folder is the simplest thing that works: a single clone gives a new developer every App, one CI configuration covers them all, and a change touching two Apps is one pull request.
$ cd acme-payroll$ git init$ git add .$ git commit -m "Import Acme Payroll apps from Factorial Code"$ git push -u origin mainAdd a .gitignore at the team root first — the agent skills are a local install, not source:
.claude/skills.agentsskills-lock.jsonWhat each App’s workspace already excludes for you (its .gitignore is written by the CLI):
variables.local.env, datastore.json, .env, storage/*, and every inherited file, which the
parent workspace owns and fcode pull regenerates.
Keep .fcode/ in the repository. It records which cloud workspace each folder points at, so a
teammate who clones the repository gets folders fcode already recognises — fcode pull in an App’s
app folder just works, with no second setup step. The cost is that its per-resource sync hashes can
conflict when two people push around the same time; resolve by taking either side and running
fcode team:pull, which rewrites them from the cloud.
Staying in sync
Section titled “Staying in sync”The cloud is the source of truth for what actually runs. A process executes the version stored in its workspace, not the one on your branch — merging a pull request changes nothing in Factorial Code until someone pushes. Treat the repository as history, review and backup, and keep one habit: pull before you work, push when you are done.
$ fcode team:pull # from the team root: clone new Apps, pull every workspace$ fcode team:status # what differs from the cloud, App by App$ git diff # …and what that changed in the repositoryfcode team:pull also picks up Apps created since your last run and refreshes each App’s
settings.json. An App removed from the team is reported but never deleted — the folder stays until
you remove it yourself.
Pushing is deliberately per App. There is no team:push: you run fcode push from inside the App’s
app folder, so a deploy is always one App at a time.
$ cd payroll-sync/app$ fcode pushPromote to production from GitHub Actions
Section titled “Promote to production from GitHub Actions”Automating a push to the dev workspace is rarely what you want: dev is where you and the console
edit interactively, and a job pushing on every merge would fight it. The push worth automating is the
promotion to production, which is exactly the fcode push the release flow leaves for a human to
run.
Two things have to be true before a job can do it:
- The release exists. Requesting a release in the console is what creates the
prod-{app}workspace, snapshots dev as a workspace version carrying that release, and grants write access to the qualifying members of the owning team. Until then there is nothing to push to. - The identity is authorized. Only operators and the App’s qualifying team members may write to a prod workspace. Create the API credential in the prod workspace — someone who cannot promote by hand cannot create one either — and store its Client ID and Secret as repository secrets.
The checkout points at the dev workspace it was cloned from, so the job has to re-point it before pushing, or it would promote onto dev. The prod slug is per App, so keep it in a repository variable:
name: Promote to production
on: push: branches: [main]
jobs: promote: runs-on: ubuntu-latest defaults: run: working-directory: payroll-sync/app steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" - run: npm install -g @factorialco/fcode-cli - run: fcode login -c "$CLIENT_ID" -s "$CLIENT_SECRET" env: CLIENT_ID: ${{ secrets.FCODE_CLIENT_ID }} CLIENT_SECRET: ${{ secrets.FCODE_CLIENT_SECRET }} # Without this the push would land on the dev workspace the folder was cloned from. - run: fcode remote:set "$PROD_WORKSPACE" env: PROD_WORKSPACE: ${{ vars.FCODE_PROD_WORKSPACE }} # e.g. prod-b5hiralj5hpupls4otok6vxah - run: fcode add - run: fcode pushfcode add registers the resources against the newly targeted remote, which starts out tracking
nothing — the same sequence the promotion in the CLI page
performs by hand.
Three things to know before you rely on this:
- Everything is per App. The prod slug, the API credential and the
working-directoryall belong to one App, so a repository holding several needs one job (or matrix entry) each. - Prod code is meant to be read-only. This automates the promotion; it does not make the repository authoritative over dev. Changes still start in the dev workspace and reach production through a release.
- A diverged prod fails the job, which is what you want: it stops rather than discarding
something promoted another way. Resolve it by pulling prod into the branch rather than reaching for
--force.
Publishing the workspace version and moving the stable alias stays a separate, deliberate step —
see workspace versioning.