Skip to main content
Version: 12 - TBD

Git Maintenance

Every git-enabled container (a bundle, project, or workspace whose resources are synced to a git repository — see Git Integration) keeps a bare git repository on the workspace host. As you edit resources, git-sync writes new commits into that bare repo. Over time the repository accumulates loose objects — the small, individually-stored, not-yet-packed objects git creates on every write — and grows on disk. A large, never-packed bare repository (the CI/CD or content-heavy bundle of an active workspace can reach tens of gigabytes) is a common cause of host disk pressure, which can eventually block deploys and upgrades.

Git maintenance runs the standard git housekeeping commands (git prune, git gc/repack) on a bare repository to reclaim that space — safely, while the workspace keeps running. You do not have to stop the workspace or take the container offline.

note

This reclaims space inside the git bare repositories used by git-sync. To analyse and reclaim space in the workspace database (bloat, VACUUM FULL, largest tables and files), see Storage Analysis instead.

Why it is safe to run online

Git-sync writes to the same bare repository live from inside the workspace. Running a plain git gc at the same time as a concurrent push can corrupt the repository, so maintenance cannot simply shell out to git. Instead it enforces a small set of load-bearing safety invariants:

  • Quiesce. Maintenance acquires the container's git-sync lock before it touches the bare repo. Every workspace process shares this lock, so while maintenance holds it, all git-sync activity on that container pauses — no push can land mid-maintenance.
  • Heartbeat. A long gc can run for many minutes. Maintenance refreshes the lock periodically so peer processes never mistake it for a stale/abandoned lock and reap it out from under a running gc. The lock is always released cleanly at the end, even if the run is cancelled.
  • Integrity check. git fsck --full runs before maintenance (it aborts if the repository is already broken) and again after (to confirm integrity). The tiny ref files are snapshotted first as a cheap safety net.
  • Only unreachable objects are removed. Pruning removes only objects that are no longer reachable from any ref. Your reachable history is never touched.
  • Deprioritised I/O. The heavy gc/prune git process runs at idle I/O priority and lowest CPU priority (ionice/nice) so it does not starve the co-located workspace.

Access

Maintenance is exposed as two REST endpoints on the git container. They require a Cloudomation identity (your user, or an API key / webhook identity) with permission on the container:

EndpointPurposeRequired permission
POST /api/latest/{kind}/{id}/git/maintenanceStart a maintenance runupdate on the container
GET /api/latest/{kind}/{id}/git/maintenanceRead the status of the last/current runread on the container
  • {kind} is one of bundle, project, or workspace.
  • {id} is the record id of the container (a by=name query parameter is also supported to address it by name).

Because maintenance mutates the on-disk repository, the POST is gated on update permission (the same permission as committing/pushing), while reading status only needs read. See Role Based Access Control for how permissions are assigned.

Modes

The POST body is a JSON object; every field is an optional boolean. The mode is chosen by precedence: dry_run > prune_only > do_gc.

FieldEffect
dry_runAcquire the lock + heartbeat + run fsck only — no objects are removed. Validates the quiesce/integrity path with zero risk.
prune_onlyRun git prune --expire=now: remove unreachable loose objects without a repack. No large pack write, no sustained disk-I/O spike — safe to run online at any time. This is the recommended routine reclaim.
do_gcRun a (memory-bounded) git gc — a full repack. Add prune: true to pass --prune=now. This writes a new pack and causes a disk-I/O spike; run it only in a quiet maintenance window.
Always send explicit flags

If you POST an empty body {}, do_gc defaults to true and a full gc/repack runs immediately. To avoid an unintended heavy operation, always send the flags you want explicitly — e.g. { "dry_run": true } or { "prune_only": true }.

The run is detached: a full gc can outlive the request, so the POST returns immediately (with the initial status) and the work continues in the background. Poll the GET endpoint to follow progress. If a run is already in flight, the POST returns the current status with "already_running": true instead of starting a second run.

Status

The GET endpoint returns the current status as JSON (or { "state": "NONE" } if the container has never been maintained). Key fields:

  • state — one of NONE, STARTING, LOCKING, RUNNING, DONE, ERROR.
  • phase — while RUNNING: snapshot_refs, fsck_pre, prune / gc, fsck_post, done.
  • size_before_bytes, size_after_bytes, reclaimed_bytes — repository size and space reclaimed.
  • dry_run, do_gc, prune, prune_only — the effective mode of this run.
  • errornull unless state is ERROR.
  • requested_by, container, started_at, updated_at, finished_at — bookkeeping.
  1. Check status. GET the endpoint to see whether a run is already in flight and how large the repository is.
  2. Validate first (optional). POST { "dry_run": true }. This exercises the lock, heartbeat, and fsck path without removing anything, and confirms maintenance can quiesce the container cleanly.
  3. Routine reclaim. POST { "prune_only": true }. This is safe to run online and reclaims whatever loose objects are already unreachable, with negligible impact. Prefer a quieter moment, but it does not require a maintenance window.
  4. Full compaction (occasionally). POST { "do_gc": true, "prune": true } to fully repack and prune. Because this causes a disk-I/O spike, run it in a quiet window — when no integration/CI suite or other heavy workload is running — and make sure the host has enough free disk for the new pack.

Poll GET between steps until state is DONE (or ERROR).

When to run

Run maintenance when a container's bare repository has grown large or accumulated many loose objects — typically the busiest, most-frequently-synced containers (large content bundles, CI/CD bundles). prune_only is cheap enough to run regularly; reserve the full do_gc repack for occasional deep compaction in a quiet window.

Example: trigger maintenance from a flow

You can automate maintenance with a Cloudomation flow using the REST connector against your own workspace. The example below starts a prune_only run on a bundle and then polls until it finishes. Replace host, the bundle record_id, and the API key with your own.

import flow_api


def handler(system: flow_api.System, this: flow_api.Execution, inputs: dict):
host = 'your-workspace.cloudomation.io'
kind = 'bundle' # 'bundle' | 'project' | 'workspace'
record_id = inputs['record_id']
api_key = inputs['api_key'] # an API key with 'update' permission on the container
auth = {'Authorization': f'Bearer {api_key}'}
route = f'/{kind}/{record_id}/git/maintenance'

# Start a safe, online prune-only reclaim.
this.connect(
connector_type='REST',
name='start git maintenance',
schema_version='10.0',
scheme='https',
host=host,
base_path='/api/latest',
path=route,
headers=auth,
mode={
'mode_name': 'post',
'body': {'body_mode': 'json', 'json': {'prune_only': True}},
},
expected_status_code=[200, 202],
)

# Poll the status endpoint until the run reaches a terminal state.
while True:
this.sleep(15, name='wait between polls')
status = this.connect(
connector_type='REST',
name='poll git maintenance',
schema_version='10.0',
scheme='https',
host=host,
base_path='/api/latest',
path=route,
headers=auth,
mode={'mode_name': 'get'},
).get('output_value')['json']
if status.get('state') in ('DONE', 'ERROR'):
break

if status.get('state') == 'ERROR':
return this.error(f"git maintenance failed: {status.get('error')}")
return this.success(
f"reclaimed {status.get('reclaimed_bytes')} bytes "
f"({status.get('size_before_bytes')} -> {status.get('size_after_bytes')})"
)
tip

Start with {'dry_run': True} the first time you automate maintenance for a container, to confirm the quiesce and integrity path succeed, before switching to prune_only (or an occasional do_gc) for real reclaim.