Adding repositories
A fresh install indexes no repository. Entries come from two sources, merged: the repositories people add themselves and the operator's configuration. An operator entry for a namespace added here is skipped (see the rules below). One code namespace is one branch (overview).
| Path | How | Built by | Tiers |
|---|---|---|---|
| Self-service | Repositories → Add repository in the dashboard, or Add branch on a card you maintain | the code_ingest worker, in slices |
fast tier; optional compiler tiers |
| Operator-configured | an entry in LOOM_CODE_REPOS_JSON (or the scalar LOOM_CODE_* settings), then loom code backfill |
the operator, with the loom code CLI |
fast tier, plus the compiler tier where configured |
Self-service needs no operator or shell access for the fast tier. Compiler tiers are optional precision upgrades and need their language toolchain in the worker image.
Self-service repositories
Needs LOOM_CODE_SELF_SERVICE_ENABLED and the code_ingest worker. Both are enabled by
the stock development Compose deployment; custom deployments can disable either.
It also needs the repository-job sandbox:
a repository added here gets compiler tiers that execute its own build code, and its
clone talks to a host the person chose, so without the sandbox the add is refused
(SANDBOX_UNAVAILABLE) and entries already added are no longer fetched or indexed.
Of the shipped Compose stacks only the blue/green one (dev/docker-compose.colour.yml)
runs it, on a host with the AppArmor profile loaded. On the others, the released stack
included, index a repository through LOOM_CODE_REPOS_JSON
(quickstart).
Each org holds at most LOOM_CODE_SELF_SERVICE_MAX_REPOS (20) entries.
Any signed-in member of an org can add a repository and becomes its first maintainer.
The remote can be owner/name (GitHub shorthand), an HTTPS URL, or an SSH/scp-style
Git URL. GitHub, GitLab and Bitbucket are labelled explicitly; other servers use the
generic adapter. Local paths and file:// URLs are refused. The remote must answer
git ls-remote, using the optional access token for HTTPS or the worker's SSH setup.
When branch is blank, Loom follows the remote's symbolic HEAD; there is no master
assumption. The namespace is code-<name> for the default branch,
code-<name>-<branch-slug> otherwise.
The worker clones a bare mirror under LOOM_CODE_REPOS_ROOT (default
var/code_repos/self, one per Git repository, shared by its branches). It builds the
first index LOOM_CODE_BOOTSTRAP_FILES_PER_PASS (400) files per pass with a cursor, so a
restart resumes. code.repos reports progress in stage and stage_detail:
flowchart LR
queued --> cloning --> indexing --> embedding --> ready
cloning --> error
indexing --> error
The entry stays in embedding until LOOM_CODE_READY_EMBED_COVERAGE (0.95) of its
chunks carry a vector; until then code.search answers lexically
(freshness).
| Op | Role | Does |
|---|---|---|
code.repo.remote_branches |
viewer | what the Git remote shows before anything is written: provider, branches and heads, default branch, release-shaped branches, quota and readiness warnings |
code.repo.add |
viewer | add repo@branch (blank discovers the remote default), optionally more branches; access, filters, scip_producer, access_token |
code.repo.add_branch |
admin | index another branch of a repository you maintain; it inherits remote, token, prefixes and access |
code.repo.set_access |
admin | set classification (standard / restricted) and join_policy (open / approval / closed) |
code.repo.set_source |
admin | link the tracker namespaces this repository reads, primary first (tracker overview) |
code.repo.remove |
admin | retire a self-service index; confirm must equal the namespace |
Arguments are in the operations reference.
Rules:
- An org holds at most
LOOM_CODE_SELF_SERVICE_MAX_REPOS(20) active self-service entries, branches included (REPO_QUOTA). A branch already indexed, under any namespace, or whose namespace holds data or grants you do not administer, is refused withREPO_EXISTS; join an index instead. A branch of an operator-configured entry with no first index yet is refused withREPO_NOT_INDEXEDuntilloom code backfillhas run for it. - Admin grants flow between a repository's branches in one direction. The operator's
entries pass their admins to every branch of the repository, and a branch added here
passes its admins only to other standard branches added here. A restricted branch
added here takes the operator's admins only. Every sync revokes the other copies it
holds, or makes an operator admin's one a copy of the operator grant; a copy whose
granted_bynames an entry the replica does not configure is revoked too (a retired entry, or a new one a rolling deploy has not reached: grant admin on that entry again). A branch whose admins were all such copies is left with none, and only an admin grant on one of the repository's operator entries reaches it. A member who held such a copy may have granted or approved others there, themselves included: review its grants. Widening the branch again does not bring back a copy a restriction revoked; grant that admin on a sibling again. Only an admin with a grant given there, or a copy of one from an operator entry, can widen it; a copy from a branch added here does not count. - A revoke follows the same path. One made on an operator entry removes from a branch added here only a copied grant, and none from a standard one while the subject holds an admin grant given on a branch added here. A copy that is a branch's only admin stays, except on a restricted branch added here, where a revoke takes a copy whose source is not an operator entry (one whose holder keeps an admin grant given on an operator entry other than the revoked one is rewritten by the next sync instead); an operator copy kept there goes at the next sync once its operator grant is gone.
- An operator entry that names a namespace added here (
code.repo.addorcode.repo.add_branch), even one removed since, is skipped, with a warning in the server log, since the grants of whoever added it would become an operator entry's. Removing the branch does not free the name: list the entry under another namespace. A registry row that records no kind is read as the entry's access row, a namespace a member created withnamespace.registerhas no registry row, and while self-service is off the registry is not read: review a namespace's grants before listing it in each case. - The access row
code.repo.set_accesswrites for an operator entry is not listed while the entry is out of the configuration, so the namespace is not indexed; it applies again if the entry returns, and it does not count against the org's repository quota. A registry row that records no kind is listed as a branch added here once its entry is gone. - A copy on an operator namespace whose
granted_byisrepo-maintainers:<namespace>naming a branch added here, or a namespace no longer configured, stays until revoked: list those grants and revoke the ones that should not stand. - While self-service is off, a revoke on an operator entry sees no branch added here, so a copy there of the revoked grant stays when self-service is switched back on: revoke it on that branch.
- A restricted repository is never
open:code.repo.addsetsapproval. Widening a restricted index to standard needsconfirmequal to the namespace. - A private repository's token is stored encrypted with a key derived from
LOOM_JWT_SECRETand never returned. Rotating that secret makes stored tokens unusable until a maintainer enters them again. code.repo.removeretires the registry rows and purges everycode_*row of the namespace through a resumable erasure saga. The mirror is deleted when no other branch uses it. An operator-configured entry is refused withREPO_FROM_CONFIG.LOOM_CODE_SELF_SERVICE_ENABLED=falseremoves the feature (SELF_SERVICE_OFF).LOOM_CODE_INTEL_ENABLED=falserefuses everycode.repo.*op butcode.repo.remove, withPRECONDITION_FAILED, as it does every othercode.*op. A retirement with the switch off purges the index and this replica's mirror; the other replicas sweep their copy of the mirror only once code intelligence is on again.
Add a public repository and a release branch in one call:
curl -fsS -X POST "$LOOM_BASE_URL/v1/code.repo.add" \
-H "Authorization: Bearer $LOOM_API_KEY" -H 'Content-Type: application/json' \
-d '{"org":"acme","repo":"acme/widgets","branch":"main","branches":["release/2.4"]}'
The answer names each branch's namespace. Branches it could not add come back in
skipped with the reason.
Repository profiles and compiler preflight
Every entry reports a profile and provider in code.index_status. generic drives
repository-neutral search, symbols, calls, impact and context packs. The clickhouse
profile identifies the ClickHouse-only SQL registration, error-code and stateless-test
conventions; those assumptions are not inferred merely because a project contains C++
or SQL.
compiler_candidates is inferred from build manifests. A candidate without a watermark
appears in quality_warnings with the workers to enable:
| Candidate | Preflight in the worker environment | Worker |
|---|---|---|
| C/C++ | scip-clang --version (in the amd64 image); the repository's CMake configures with clang 21 |
code_scip_full, then code_scip_delta |
| TypeScript/JavaScript | Indexer and package managers included in Docker; native servers run sh scripts/install-code-toolchains.sh. Repository dependencies are prepared automatically. |
code_scip_ts_delta |
| Go | scip-go --version; go env succeeds for every module |
code_scip_go_delta |
| Python | scip-python --version; project imports resolve in its environment |
code_scip_py_delta, then code_scip_apply |
The fast tier remains usable when compiler preflight fails. code.index_status and the
Health tab expose worker errors, per-language coverage, unresolved edges, embedding
coverage and freshness. PR/review history currently uses the GitHub API; other providers
still receive Git history and code intelligence, with an explicit onboarding warning.
Operator-configured repositories
LOOM_CODE_REPOS_JSON holds one object per indexed branch. The entry fields,
prerequisites (image, embedder, writable mirrors, GitHub token), the first index
(loom code init, backfill, backfill-prs, scripts/ops/code_intel_bootstrap.sh),
the compiler tier and other branches are in the
code index operations runbook. The worker
never starts an operator-configured entry; one without a git_head watermark is skipped
until loom code backfill runs. Every setting is in the
configuration reference.
Who can read
Code namespaces follow the usual grant rules
(identities and namespaces,
tokens and grants): a token must name the code
namespace (or *), or the call fails with AUTH_INSUFFICIENT. Three switches change
that:
| Switch | Effect |
|---|---|
LOOM_CODE_ORG_READABLE=true, or the entry's org_readable |
every authenticated principal of the org reads without a grant (default off); writes stay grant-driven |
LOOM_CODE_REPO_PUBLIC=true, or the entry's public |
the namespace registers join_policy=open and discoverable, so any org member joins as a reader at once; otherwise the policy is approval |
classification: "restricted" (entry, or code.repo.set_access) |
never org-readable or public; a maintainer approves each reader, and every read needs a token naming the namespace plus a live direct grant |
A code.repo.set_access row wins over the configured classification and join policy
and survives restarts. code.refresh (writer) and the admin ops always need a grant.
Failure modes
| Symptom | Cause and fix |
|---|---|
| A configured entry never indexes | no git_head watermark: run loom code init and loom code backfill; or the namespace was added from the page (removed since or not), and the log says the entry is skipped: list it under another namespace |
Adding a repository answers SANDBOX_UNAVAILABLE |
this deployment cannot run the sandbox: list the repository in LOOM_CODE_REPOS_JSON instead (unless LOOM_CODE_SANDBOX_REQUIRED is set, which skips those too), or see the sandbox requirements |
A self-service card stays at error |
read stage_detail in code.repos; a failed clone retries next pass (timeout LOOM_CODE_BOOTSTRAP_CLONE_TIMEOUT_S) |
| A private self-service repository stops fetching | LOOM_JWT_SECRET was rotated; re-enter the token |
An entry is missing from code.repos |
check loom.code.repos_config_errors; code.repos also lists only what the caller may read |
| Revoking or lowering an admin answers busy | a revoke on a code namespace first re-reads loom.code_repos, so it sees the branches added here from another process, and answers busy while that read fails; retry once the read succeeds |
| A restricted branch added here has no admin | its admins were all copies from branches added here, which the sync revokes; grant admin on one of the repository's operator entries (with none, the operator lists a branch of it in LOOM_CODE_REPOS_JSON first) |
| A new maintainer lacks the other branches | the admin grant was made before this process read loom.code_repos (that process logs not synced the first time); the next admin grant or approval on a branch of the repository, or a branch added, copies it; or grant it again |
| Answers are lexical only | the entry has not reached LOOM_CODE_READY_EMBED_COVERAGE (freshness) |