Git remote
Included from Basic tier
JSON:API type: git_remote.
The v2 OpenAPI document declares this resource's schema; the resource-field coverage gate holds this page to it.
A git remote is a project-scoped repository registration: the URL plus the credentials templates and template versions use to fetch it.
Endpoints
All under /api/projects/{project_code}/git_remotes:
| Verb | Path | Behaviour |
|---|---|---|
GET | …/git_remotes | Index — each remote gated by read on the project's …/git_remotes rule path. meta.authorisations reports update/delete on that path, plus default_periodic_fetch_interval (added only when the project itself is readable). Accepts the shared Ransack filter grammar; the sensitive columns are excluded from the allowlist, so filter[user_*], filter[password_*] and filter[ssh_key_data_*] are rejected 400. Default order: created_at ascending, id ascending. |
POST | …/git_remotes | Create — project readable plus update on …/git_remotes, 404-on-deny. 201, or 422 with the model errors. A credentialed create on a server with no encryption keys configured answers 503 (see below). |
GET | …/git_remotes/{id} | Show — a missing project, a remote outside the project, or a read denial all answer 404. |
PATCH/PUT | …/git_remotes/{id} | Update — same 422 model-error and credentialed-write 503 contract as create. |
DELETE | …/git_remotes/{id} | Destroy — but an archived remote still referenced by changes keeps its row and has its credentials stripped instead: 200 with the meta message "This Git remote cannot be deleted as it is used by a change, its credentials have been removed." A hard delete answers 204. |
A remote is also reachable as a sideloaded resource: include=git_remote on
the project's templates index or a single-template show carries it in the
compound document's included, serialized exactly as the show above —
masked credentials and all.
Writes on a server with no encryption keys (503)
Credentials are encrypted before they are stored, so a create or update that
supplies a credential (user, password, passphrase or ssh_key_data) on
a server with no encryption keys configured cannot be honoured. It answers
503 Service Unavailable with the named detail
git remote credentials cannot be stored: no encryption keys are configured on this server
— a known, operator-fixable configuration state (set OPSCHAIN_PRIMARY_KEY
and OPSCHAIN_KEY_DERIVATION_SALT), deliberately not a 500. Credential-free
writes and destroys never encrypt anything, so they never answer this 503.
Attributes
| Attribute | Type | Meaning |
|---|---|---|
name | string | The remote's name, unique within the project; how templates reference it (git_remote_name). |
url | string | The fetch URL as stored — any userinfo was stripped on write, so exposing it leaks nothing. A transport URL: http://, https://, ssh://, git:// or the scp-like user@host:path form. A filesystem path or a file:// URL is refused by name on create and on a URL change (Url must be an http(s)://, ssh://, git:// or user@host:path remote, a 422 on v1 / invalid_argument with the url field violation on v2) before any connectivity probe, unless the server's deploy-authoritative security.allow_local_git_remotes admits it (opschain docs config). At most 2048 characters: a longer value is refused by name on create and update (Url is too long (maximum is 2048 characters), the same shapes); the stored column is unbounded text. |
public_url | string, nullable | A display URL override; null when unset. Bounded like url (Public url is too long (maximum is 2048 characters)). |
archived | boolean | Archive flag (update-only in effect: create permits it and then ignores it). |
disabled | boolean | Disable flag (nullable column, coalesced false on read). |
periodic_fetch_interval | integer, nullable | Per-remote override of the periodic fetch cadence in seconds (60–86400); null means the remote inherits the default reported in the index meta (project settings override → global settings override → compiled default 900) — "The periodic fetch" below has the rule. A supplied null clears the override. |
created_at / updated_at | string (ISO8601, 6 fractional digits) | Row timestamps. |
last_fetched_at | string (ISO8601), nullable | When the periodic fetch sweep last fetched this remote; null until the first sweep. Written on every sweep fetch, quiet or not. |
last_fetch_outcome | string, nullable | How the last periodic fetch went: ok, failed (see last_fetch_error), or skipped (the remote stopped being active between the due test and the fetch). Null until the first sweep. |
last_fetch_error | string, nullable | The credential-free cause of the last failed periodic fetch (the same text the error:git_remote:fetch event carries); null after a successful fetch. |
consecutive_failures | integer | How many periodic fetches in a row have failed; 0 after a success. A rising count with the same last_fetch_error is one outage, not many. |
next_fetch_at | string (ISO8601), nullable | Computed — when the remote is next due: last_fetched_at plus its effective interval. Null while the remote is archived, disabled, switched off by a non-positive effective interval, or has never been fetched (due at the next sweep). |
floating_versions | integer | Computed — the unarchived template versions on this remote with float_git_rev set, the versions the sweep repins when their revision's tip moves. |
last_repin_at | string (ISO8601), nullable | When the sweep last repinned a floating template version on this remote; null when it never has. |
watch_default_branch | boolean | Whether the fetch watches the remote's default branch for ref moves (default true): with it on, a push to the default branch records info:git_remote:ref_moved with no trigger or floating version naming the branch; a busy repository whose default branch never rests turns it off per remote — "Ref-move events" below. Writable on create and update. |
last_ref_move_at | string (ISO8601), nullable | Computed — when a watched ref of this remote last moved (the newest recorded move in the ref snapshot); null while none has. |
user | string, nullable | Masked. "{{hidden}}" when a username credential is stored, null when none is. |
ssh_key_data | string, nullable | Masked. "{{hidden}}" when an SSH key is stored, null otherwise. |
password | string, nullable | Masked. "{{hidden}}" when a password/passphrase credential is stored and the url is not an SSH url; null otherwise. |
passphrase | string, nullable | Masked. The same stored credential presented as a passphrase when the url is an SSH url (explicit ssh:// scheme, or the schemeless scp-like user@host:path form); null otherwise. |
credential_ref | string, nullable | The shared git credential this remote references (write-accepted on v1, exposed on v2): fetch/clone resolve the credential at use time, so updating the credential applies to every referencing remote on its next use. Null when the remote stores inline credentials (or none) — a remote never holds both. |
Credential redaction semantics
Stored credential values are encrypted at rest and never materialised by
this read path at all: the reader selects only a boolean presence per
column, and the serializer maps presence to the placeholder {{hidden}} and
absence to null. password and passphrase are two views of one stored
column, split on whether the URL is SSH. On writes the three-way distinction
is load-bearing: an omitted credential key preserves the stored value,
while a key supplied as null or "" clears it — so echoing a read payload
(all {{hidden}}) back on an update would overwrite credentials; send only
the keys you mean to change.
Shared credentials (credential_ref)
Instead of inline values, a write may reference a shared git credential by
id — or by name within the remote's scope (the project's own registry, then
the global one). The referenced credential must live at or above the
remote's node: its project, or the global registry; another project's
credential is refused. A write carrying both a credential_ref and inline
credential values (URL userinfo included) is refused
(Credential ref cannot be combined with inline credentials - supply one or the other); supplying a reference clears any stored inline values (the
remote flips to the reference), and supplying inline values while a
reference is in place is refused until the reference is cleared
(credential_ref: "") in the same write. The reverse migration is the
ExtractGitRemoteCredential action — see
opschain docs resources/git_credential.
The periodic fetch
The server keeps every active remote's mirror warm and every floating
template version on its branch without anyone pressing Fetch revision.
Once a minute the cron leader (one replica at a time, whichever holds the
scheduler lease; nothing runs while the system is quiesced) sweeps for
remotes that are due — unarchived, not disabled, on an unarchived
project, with a positive effective interval that has elapsed since
last_fetched_at (a remote never fetched is due at once) — and, oldest
fetch first, up to two hundred per pass:
- Fetches the mirror (
git fetch --prune) under the remote's credential — the inline credentials or the referenced shared credential, resolved at fetch time — and records the outcome on the remote:last_fetched_at,last_fetch_outcome,last_fetch_error,consecutive_failures. A failure recordserror:git_remote:fetch(withcauseandconsecutive_failures); a fetch that moved any ref recordsinfo:git_remote:fetched(never a quiet one). A remote that stays down is retried at its interval and the streak grows. - Repins the floating versions: for every unarchived, unlocked
template version on the remote with
float_git_revset, no refresh in progress and a resolved commit, the stored revision is resolved in the fetched mirror; when the tip differs from the sealed commit the version takes the manual Fetch revision path — pack pins re-resolved (explicit pins kept), the pinned assets' active windows rolled onto the new commit, their action catalogs refreshed — under the git identity policy as the version's creator, andinfo:template_version:rev_repinned— on the chain of theinfo:git_remote:ref_movedevent the same fetch recorded for the branch (the branch a floating version follows is always watched), itsref_moved_event_idnaming the move — (from→to) goes on the record andlast_repin_atis stamped. A repin the sweep cannot perform recordserror:template_version:float_refreshand the version'sfloat_refresh_error, cleared by the next successful repin (opschain docs resources/template).
The effective interval of a remote is, in order: its own
periodic_fetch_interval; else the owning project's settings document
git_remote.periodic_fetch_interval; else the global settings document's
git_remote.periodic_fetch_interval; else 900 seconds (opschain docs settings-reference). The index meta's default_periodic_fetch_interval
reports the inherited value. A non-positive inherited value switches the
periodic fetch off for the remotes inheriting it (next_fetch_at reads
null); a disabled or archived remote is never fetched.
A manual Fetch revision (templates version upsert --fetch, the GUI's
Fetch revision) still works exactly as before and does not touch the
sweep's record — last_fetched_at is the sweep's own.
Ref-move events
The fetch is the one source of truth for "a ref moved": after every
successful fetch — scheduled, hinted by a webhook, or manual — the server
compares the remote's watched refs against the ref snapshot it kept
from the previous fetch and records one info:git_remote:ref_moved per
watched ref that moved, carrying the SHAs it observed (opschain docs events). A ref nobody watches is silent, so a busy repository does not
fill the ledger. The watched set is the union of:
refs/heads/<default branch>— the remote's HEAD as the mirror recorded it — unless the remote'swatch_default_branchis off;- every ref a floating template version on the remote follows
(
float_git_rev); - every branch or tag an enabled trigger names: a trigger whose
eventscarriesinfo:git_remote:ref_moveddeclares the refs it cares about through its conditions (event.data.remote eq,event.data.ref eq | in | contains,event.data.kind eq), bounded by its scope path; one with no ref condition watches every branch and tag of the remotes it names. Other refs (refs/pull/*, notes) are never watched.
The set is recomputed on every fetch, so a new trigger takes effect on the
remote's next fetch. A ref the trigger names that already exists is seeded
into the snapshot silently (no event for a branch that was already there);
a ref the fetch itself brought records created; a watched ref that is
gone records deleted and leaves the snapshot; a ref no longer watched
leaves the snapshot without an event. The remote's first fetch after
upgrading seeds the whole snapshot and records nothing.
What change means. fast_forward — the new tip descends from the
previous one (commits counts what it brought); forced — it does not:
history was rewritten, and warn:git_remote:forced_push is recorded
beside the event every time, listener or not; created and deleted as
the names say. Coalescing: moves of one ref observed inside the
remote's effective fetch interval extend the open ref_moved event — the
first old_sha, the latest new_sha, commits summed, extended: true
and fetch recording every cause (scheduled+hint) — so ten commits
pushed in a minute are one deployable state; a forced move or a deletion
always opens its own event, and a fast-forward after a forced move does
too.
Fetching now, and webhooks as hints. GitRemoteService/FetchGitRemote
(opschain git-remotes fetch <project>/<name>, update on the project's
…/git_remotes rule path) fetches the remote at once over the same lane
and waits for it; an archived or disabled remote is refused as not found.
A verified inbound-webhook delivery is a hint that takes that lane
without waiting: a delivery to a connection whose git_remote names this
remote, or a GitHub push delivery naming this remote's repository,
schedules the fetch now — the payload's claimed SHA is discarded; the
fetch decides. Hinted fetches are rate-limited per remote: one in flight,
hints that arrive during it coalesce into one follow-up fetch, and hinted
fetches stay at least thirty seconds apart (deferred, never dropped), so
a hostile or misconfigured sender causes at most a couple of fetches a
minute. Every fetched / ref_moved event names its cause in fetch:
scheduled, hint or manual. opschain docs inbound-webhooks has the
delivery side.
GitRemoteRef — the ref snapshot
GitRemoteService/ListGitRemoteRefs (v2; opschain git-remotes refs <project>/<name>) reads the snapshot, ref name ascending, authorised
like a read of the remote:
GitRemoteRef field | Meaning |
|---|---|
ref | The full ref name (refs/heads/main, refs/tags/v1.2.0). |
sha | The commit the ref pointed at when the platform last fetched it. |
kind | branch or tag. |
observed_at | When the platform last observed the ref — every fetch, moved or not. |
last_change | The last recorded move: fast_forward, forced, created or deleted; unset while the ref has not moved since it was first watched. |
last_moved_at | When that move was observed; unset with last_change. |
Health summary
GitRemoteService/SummarizeGitRemoteHealth (v2) is the operations read
over the sweep, estate-wide: one GitRemoteHealthRow per unarchived
remote whose project /git_remotes rule path the caller may read, plus
GitRemoteHealthTotals over those rows. failing_only keeps only the rows
whose last fetch failed; the totals always count every visible remote.
Compositions without the PostgreSQL git-remote estate answer
ERROR_REASON_STORE_POSTGRES_REQUIRED.
GitRemoteHealthRow field | Meaning |
|---|---|
id, project_code, name | The remote's identity. |
disabled | Whether the remote is disabled (never fetched while it is). |
last_fetched_at, last_fetch_outcome, last_fetch_error, consecutive_failures, next_fetch_at, floating_versions, last_repin_at | As on the resource above. |
repins_24h | Floating repins the sweep performed on this remote in the last 24 hours (counted from the info:template_version:rev_repinned events). |
due | Whether the remote is due now — never fetched, or its effective interval has elapsed. |
GitRemoteHealthTotals field | Meaning |
|---|---|
remotes | Visible unarchived remotes. |
due | Of those, due now. |
failing | Of those, whose last periodic fetch failed. |
repins_24h | Floating repins across the visible remotes in the last 24 hours. |
floating_versions | Floating template versions across the visible remotes. |
Relationships and links
| Name | Meaning |
|---|---|
project | To-one project (the owning project). |
links.self | /api/projects/<code>/git_remotes/<id>. |
links.project | /api/projects/<code>. |
The v2 service — GitRemoteService
ListGitRemotes / GetGitRemote (by id or name) / CreateGitRemote /
UpdateGitRemote / DeleteGitRemote carry this surface typed, with the
same {{hidden}} credential masking, the
default_periodic_fetch_interval meta, the credentials-removed delete
arm (which also clears a credential_ref), the keyless-write 503
typed as unavailable + ERROR_REASON_SERVER_NOT_CONFIGURED,
credential_ref on the payloads and writes, the periodic-fetch
health fields and watch_default_branch on every GitRemote;
SummarizeGitRemoteHealth is the estate-wide health read above and
ListGitRemoteRefs the ref snapshot. See opschain docs api-v2.
Related pages
opschain docs resources/template— templates pin a git remote by name.opschain docs resources/git_credential— the shared credential registry remotes reference viacredential_ref.