A forge is the place your code lives: GitHub or Cursor Origin. Yard needs two things from it. It must be able to read your repos, private ones included. And it wants to hear about each push, so a deploy starts in seconds rather than minutes.
This chapter explains how Yard gets both, and why it trusts the forge for the code but not for the news.
Yard is an app on the forge#
Yard does not use your personal account, a deploy key per repo, or a token that you paste and forget. It connects as an app: a GitHub App, or a Cursor Origin app. You create the app once and install it on your account. From then on, the forge treats Yard as its own small actor, with its own permissions.
That choice pays off in three ways:
Narrow permissions. The app can read code and create repos, and nothing else. It cannot see your billing or your settings.
No long-lived token. Yard holds a private key and makes a fresh token whenever it needs one. Each token expires within the hour.
One install covers every repo. A new repo needs no new key and no new webhook. The installation already covers it.
You can connect both forges to one host. yard forge ls shows what is connected.
Setting up GitHub#
yard forge setup githubThe command runs on your laptop. Your browser opens on GitHub’s “Register new GitHub App” page, already filled in: a name, the webhook URL https://<your-yard>/hooks/github, the push event, and three permissions:
Contents: write, to read code, and to mint the one-repo push token described in A token for one repo.
Metadata: read, which every GitHub App needs.
Repository creation: write, for
yard repo new.
You click “Create GitHub App”. That is the only click on that page.
Then GitHub sends the app’s ID, private key, and webhook secret back to the command, and the command hands them to Yard. You copy nothing and paste nothing, and the key never appears on your screen. This is GitHub’s app manifest flow. The command runs a tiny web server on 127.0.0.1 for the length of the setup. GitHub sends your browser back to it with a one-time code, and the command trades that code for the key.
Next, the install page opens. Install the app on your account, choose “All repositories”, and the command notices when the installation lands. It waits up to ten minutes.
A few flags cover the other cases:
--org my-orgcreates the app in an organization.--publiccreates an app that other accounts can install. A private app installs only on the account that owns it.--existingconnects an app you already have, or one whose setup broke halfway. It asks for the app ID, the key file, and the webhook secret, or takes them from--app-id,--key, and--webhook-secret. Use it on a machine with no browser, too.--newreplaces a connected app with a new one.
The webhook URL comes from the address you logged in with. That is why you run this after yard login with the public URL: GitHub must reach Yard on that name.
Setting up Cursor Origin#
yard forge setup originThe command prints the values for the form, and the public key that Yard made for the app. Create the app in Origin’s settings with:
the webhook URL
https://<your-yard>/hooks/origin,the event
repository.pushed,the scopes
repository:contents:readandnamespace:repositories:create.
Paste the app ID at the prompt. Then the install page opens. Install the app on your account and choose all repositories, and the command waits until it sees the installation.
All repositories#
Both setups ask you to install the app on “All repositories”. Here is why.
An installation on “selected repositories” sees exactly the repos you picked. Deploys work fine for those repos. But yard repo new creates a repo that did not exist when you picked them, so the installation cannot see it, and the first deploy of the new app fails. “All repositories” has no such gap.
If “all” feels like too much, use a separate account, or an organization, for the apps Yard runs and creates. Install Yard there, on all of its repos. Your other repos stay out of reach. On GitHub, Yard keeps one installation per account, so it can serve both your personal repos and the organization’s.
yard forge setup and yard status --full warn about an installation on selected repos, and link to its settings page.
Tokens#
Yard keeps each forge’s app ID, installations, and webhook secret in /srv/yard/apps/yard/data/<forge>/state.json, with the private key in key.pem next to it. Both files have mode 600.
When Yard needs to read a repo, it does what the forge asks of every app:
It signs a short JSON Web Token with the private key. The JWT lives for a few minutes.
It trades the JWT for an installation token, scoped to the installation that owns the repo.
It uses that token for
git ls-remoteorgit fetch.
Yard keeps the installation token in memory until a minute before it expires, and many checks share it. Nothing long-lived sits on disk except the key, and the key only signs requests to the forge.
The token never lands in a clone. It reaches git through an environment variable and a credential helper, so it is not in .git/config, not on a command line, and not in a log.
A public repo needs no installation at all. Git asks for credentials only when the server demands them, and a public repo never does. That is how a fresh host can follow the public Yard repo itself. Yard still checks through a connected forge, so connect one first.
Webhooks#
When you push, the forge sends a webhook to Yard. Here is everything the handler does:
For GitHub, it checks the signature.
X-Hub-Signature-256is an HMAC of the body with the webhook secret. A bad or missing signature gets a 401 and nothing else happens.It reads which repo moved. That is the only thing it takes from the body.
It asks for a check of the push apps that follow that repo.
It answers at once. The check runs after the answer.
An installation event, such as a new account or a change of selected repos, makes Yard read its installations again. Every other event is ignored.
Notice what the handler does not do. It never reads the commit from the body, and it never starts a deploy. The check that follows asks the forge for the branch tip itself, with git ls-remote.
Yard is this suspicious because webhooks fail both ways. Some never arrive: the forge had a bad minute, or Yard was restarting. And anyone can send a request to a public URL. So Yard treats a webhook as a doorbell, not a delivery. A fake ring costs one git ls-remote. A missed ring is caught by the timer, which checks every app every five minutes anyway. The webhook makes deploys fast. The timer makes them certain. Neither trusts the other’s payload.
Cursor Origin sends no signature. That is safe for the same reason: the body can only cause a check, and the check trusts nothing but the forge.
A new repo, from the laptop#
yard repo newRun it in a checkout that has a commit and no origin remote. Yard asks the forge to create a private repo with the checkout’s name, on the account where Yard is installed. Then the command adds it as origin and pushes your branch, with your own git credentials. It never runs git add or git commit for you.
With more than one account or both forges connected, --owner and --forge pick one. A name as the first argument replaces the directory name.
A token for one repo#
An app that makes other apps, such as a builder that writes code and pushes it, needs to push to a repo. Yard can mint that access without sharing its key:
POST /api/v1/repos/token {"repo": "https://github.com/you/new-app"}The answer is a GitHub installation token with contents write on that one repo, valid for one hour. Plain git uses it over HTTPS, with the user x-access-token and the token as the password. The journal records the repo, never the token. Cursor Origin does not offer this yet.
Checking the connection#
yard status --fullAmong the host checks are a few rows per connected forge: the installations, the cached token, the last webhook that arrived, and the last check that worked. A row that is not ok says what to do next.
By the way#
Yard posts nothing back to the forge. There is no commit status. The record of each deploy lives in
yard app status,yard app logs --deploy, and the journal.An app’s
repois the full URL, such ashttps://github.com/you/notes. The host in the URL picks the forge.yard forge setup githubon a host that already has an app prints the app, its installations, and a link to add another account. Then it stops.