Context Platform
Documentation

Version Latest

Project lookup

How a workspace folder is bound to its Govern project from its repository addresses, the canonical form of a repository address, and the registration event clients refresh on.

The Editor, the Agents app and the CLI bind every workspace folder to a project by asking Govern which project its repository belongs to. The answer decides which project context, policies and rules apply in that folder.

The lookup

POST /api/v1/context/project/lookup
Authorization: Bearer <access token>
Content-Type: application/json

{"repoUrls": ["git@gitlab.example.com:group/sub/name.git", "https://gitlab.example.com/group/sub/name"]}
FormUse
POST /api/v1/context/project/lookup with {"repoUrls": [...]} or {"projectId": "..."}Up to 8 candidate addresses in the client's order of preference; body at most 16 KiB, exactly one JSON object
GET /api/v1/context/project?repoUrl=...&repoUrl=...The same lookup in a query string (up to 8 candidates)
GET /api/v1/context/project?projectId=...A known project
GET /api/v1/context/projectThe organisation-level context
GET /api/v1/context/agent/project?repoUrl=...The endpoint agent, with its device token

Use the POST for long candidate lists: eight long addresses in a query string can exceed a proxy's request line limit.

The answer:

{
  "projectId": "6f1c...",
  "projectName": "Payments API",
  "projectKey": "payments-api",
  "spaceId": "0a9e...",
  "resolvedVia": "repo_url",
  "matchedCandidate": 1,
  "repoKey": "gitlab.example.com/group/sub/name",
  "classification": "confidential",
  "spaces": [],
  "sources": [],
  "activeSources": 3
}

Client rule. Bind a folder only when resolvedVia is repo_url (or project_id) and projectId is set. org_fallback means no candidate is registered to a project the caller may read; the folder is not bound. The branch is recorded by clients and not used: a repository belongs to one project whatever branch is checked out.

A GET with at most one repoUrl answers in its original shape, without projectName, projectKey, matchedCandidate and repoKey, for older clients.

Errors

StatusCodeWhen
422context.space.invalid_requestNo candidate is a repository address, more than 8 candidates, or both projectId and candidates
409context.space.repo_ambiguousThe first matching candidate is a codebase source of two readable projects, or bound in the Directory to one and a codebase source of another
429context.project.rate_limitedOver KEYSTONE_CONTEXT_PROJECT_LOOKUPS_PER_MINUTE (default 60 per person, GET, POST and agent route together); Retry-After given
403The matched project's space (or the organisation's, for the fallback) cannot be read
404context.project.not_foundA projectId that is unknown, has no space yet, or names a project the caller cannot read

Canonical repository address

Registration and lookup use one canonical form, compared for exact equality, never by prefix or pattern:

host/segment/.../name
RuleDetail
Accepted formshttps://, http://, ssh://, git:// URLs, scp-like [user@]host:path, and bare host/path. file://, local paths and other schemes are refused
CharactersPrintable ASCII only; non-ASCII is refused everywhere, so a look-alike host can never produce another repository's key
CredentialsA user name is dropped only on ssh:// and scp-like addresses. A password anywhere, and any user information on https://, http:// and git://, is refused, and the error never repeats the address
HostLower-cased DNS name or IPv4 address; the port is dropped; IPv6 literals are refused
Path2 to 20 segments (GitLab subgroups supported), lower-cased; one trailing / and one trailing .git removed; empty, . and .. segments refused; only %20 decoded
Azure DevOpsssh.dev.azure.com:v3/{org}/{project}/{repo} maps to dev.azure.com/{org}/{project}/_git/{repo}

Examples: https://GitLab.com/Group/Sub/Name.git/ becomes gitlab.com/group/sub/name; git@ssh.dev.azure.com:v3/acme/My%20Project/payments becomes dev.azure.com/acme/my%20project/_git/payments.

How a registration is found

A repository is registered to a project in the Directory's project repositories, or as a codebase source of a project space. For each candidate in order, the first rule that applies decides:

  1. The Directory binds the key to a project whose space the caller may read: that project (unless a codebase source with the key is in another readable project: 409).
  2. Codebase sources with the key are in exactly one readable project space: that project.
  3. They are in two or more readable project spaces: 409.
  4. Otherwise the next candidate is tried.

Only readable projects take part. A repository registered to a project the caller cannot read gives exactly the answer of an unregistered one, so the lookup cannot be used to learn about projects one cannot see. Each successful lookup is audited as context.project.resolve, without the addresses.

Registration event

When the repositories of a project change (in the Directory or as codebase sources), the feed carries project.repositories_changed to readers of the project's space, with ids and counts only, never an address. Clients refresh their folder bindings with the lookup when they receive it, and after reconnecting with a resume gap. See Real-time feed.

Older registrations

Directory rows stored by earlier releases in other forms are rewritten to the canonical form by the job context.repository_backfill, every 10 minutes, per organisation, idempotently. Rows that cannot be converted, and rows that would give two projects the same key, are left unchanged and reported, as counts, in repositoryIssues of GET /api/v1/context/status (organisation administrators). The console's project page shows them and asks an administrator to fix the project's repositories.

Last reviewed 17 October 2026View as Markdown

Project lookup · Docs · Neural Inverse