Remote
Register the servers hammerkit can send runs to and choose the default location.
Experimental. This page covers the registry and signing in. Sending a run to a
server (--on, running a task with a runner:) arrives in follow-up releases, see
the remote compute spec.
remote manages the list of hammerkit servers your machine knows about. Each one
is registered under a short name, which later features (--on <name>, a task's
runner:) refer to instead of a URL.
hammerkit remote add build https://build.example.com --issuer https://login.example.com
hammerkit remote login build
hammerkit remote accounts build
hammerkit remote publish payments build
hammerkit remote approvals build
hammerkit remote approve run_Qx3k9ZpL2mW7vN4a build
hammerkit remote list
hammerkit remote use build
hammerkit remote remove buildExcept for publish, the commands only touch your host config and talk to the server,
so they work with or without a build file in the working directory.
Commands
| Command | What it does |
|---|---|
remote add <name> <url> --issuer <url> | Register a server. --issuer is the OAuth issuer the server accepts tokens from, written exactly as the iss of its tokens (some providers end it with a slash). --client-id, --audience and --scope pin what a login asks for, see Signing in. |
remote list | Show the registered servers and which one is the default. |
remote use <location> | Set the default location: local, auto or a registered name. |
remote remove <name> | Forget a server. If it was the default, runs are local again. |
remote login <name> | Sign in to the server with your identity provider. |
remote logout <name> | Forget the sign-in. |
remote accounts [name] | Show who you are on the server and which service accounts you may use. Without a name, the default server. |
remote publish <definition> [server] | Publish the build file of the working directory (or the one --file names) and the files it includes under a name, see Publishing. |
remote approvals [server] | List the runs that wait for your approval. |
remote proposals [server] | List the proposed versions of build files you made or may approve, see Proposing. |
remote diff <name@hash> [server] | Show what a proposed version changes. |
remote approve <run or name@hash> [server] | Let a run go ahead, or publish a proposed version. --reason <text> is optional. |
remote reject <run or name@hash> [server] | End a run, or drop a proposed version. --reason <text> is shown to who asked for it. |
Re-running remote add with the same values changes nothing. To point an existing
name at a different server pass --force, so a typo can't silently redirect your
runs.
local and auto are reserved and can't be used as server names.
Host config
Registrations live in ~/.hammerkit/config.yaml. Set HAMMERKIT_CONFIG to use a
different file.
servers:
build:
url: https://build.example.com
issuer: https://login.example.com
run:
on: buildThe config is deliberately not part of the project's build file. A checked-in file naming a server URL could redirect your source code and OAuth tokens, so the decision of where to send work stays on the machine.
URLs
Server URLs must use https. Plain http is accepted only for localhost,
127.0.0.1 and [::1], for local testing. URLs with credentials, a query string
or a fragment are rejected, and a trailing slash is dropped.
Publishing a build file
A server runs published build files, it does not take task definitions from the one
who runs them. remote publish sends yours:
$ hammerkit remote publish payments build
published payments to build: sha256:9f2c61d0... (3 files)
tasks and services: build, deploy, more:lintIt reads the build file with the parser of a run and sends it together with every
local file it includes or references, as text. That is the bundle, up to 200 files
and 1 MiB. Includes have to be below the directory of the build file, because a
server could not reproduce anything else; git includes are not sent, the server
fetches them and wants them pinned to a commit. The version printed is the hash of
the bundle, computed here the same way the server computes it, so the same files
always have the same version. Publishing what a server already has says already published and changes nothing.
You need the publish permission for that name on the server, see
entitlements. Without a server name the default
server is used, and --file <path> publishes another build file.
Proposing a version
With only the propose permission for the name, the same command proposes the
version. It does not become a version and can not run until somebody who may publish
that name approves it:
$ hammerkit remote publish payments build
proposed payments to build: sha256:4be1a07c... (3 files)
tasks and services: build, deploy, more:lint
it can not run before somebody who may publish payments approves it, until 2026-10-11T11:41:00.000ZThe one who may publish reads what changes before deciding:
$ hammerkit remote proposals build
build: 1 proposed
payments@sha256:4be1a07c... proposed by coding-agent, waits for your approval
tasks and services: build, deploy, more:lint
decide before 2026-10-11T11:41:00.000Z
$ hammerkit remote diff payments@sha256:4be1a07c... build
payments@sha256:4be1a07c... on build, proposed by coding-agent (https://login.example.com)
compared with sha256:9f2c61d0...
--- .hammerkit.yaml (changed)
tasks:
build:
- cmds: [npm run build]
+ cmds: [npm run build, ./upload.sh]
$ hammerkit remote approve payments@sha256:4be1a07c... build
approved payments@sha256:4be1a07c... on build, it is publishedThe diff shows the lines that changed with two lines around them, in full and with
control characters replaced by ?, so nothing hides in it. It shows the files of the
bundle, not what the commands in them do. remote reject drops the proposal, with a
--reason the one who proposed it reads in remote proposals. Nobody approves their
own proposal, and what you may not decide does not exist for you.
Approving runs
A run on a service account that its caller may only request waits for an admin of that account to approve it. If you are an admin of the account:
$ hammerkit remote approvals build
build: 1 waiting for your approval
run_Qx3k9ZpL2mW7vN4a requested by 4f1c2b (https://login.example.com)
payments sha256:9f2c61d0...
tasks: deploy
account op-payments-prod, source ref refs/heads/main, env=staging
needs the approval of an admin of op-payments-prod
decide before 2026-10-11T10:41:00.000Z
$ hammerkit remote approve run_Qx3k9ZpL2mW7vN4a build
approved run_Qx3k9ZpL2mW7vN4a on build, it is queuedWhat you see is what the decision rests on: who asked, which version of which build file, the tasks, the service account and where the code comes from. The version is the one the run is pinned to, so what you approve is what runs. You see the runs on the accounts you are admin of, and no others.
Signing in
remote login uses the OAuth device authorization grant, so it works on a machine
without a browser:
$ hammerkit remote login build
signing in to https://build.example.com through https://login.example.com
client hammerkit-cli, audience hammerkit, scope openid api://hammerkit/.default
To sign in, open https://login.example.com/activate and enter the code ABCD-EFGH
signed in to build as 4f1c2b
service accounts: op-payments-dev, op-web-devOpen the address on any device, sign in as you normally do, and enter the code. The
server tells hammerkit which identity provider client, audience and scope to use, so
there is nothing to configure beyond remote add. The --issuer you registered has to be one the
server accepts, otherwise the login stops before the browser step.
The server is only trusted with that once. The first successful login pins the client,
audience and scope in the host config, and a later login refuses a server that asks
for something else (register it again with remote add --force if the change is
expected). Look at the line printed before the code: a client or audience you do not
recognise, such as one of a cloud provider's own APIs, means stop. To not rely on the
first use, give them to remote add (--client-id, --audience, --scope), the
values come from whoever runs the server. Before a token is sent to the server,
hammerkit checks that it was issued by the registered issuer for the pinned audience,
so a token for another service never leaves your machine. offline_access is dropped
from the scope: no refresh token is requested.
What is stored is one short-lived access token in ~/.hammerkit/credentials.json
(HAMMERKIT_CREDENTIALS picks another file), readable by you only (a file others can read is not used; hammerkit says how to fix it). A refresh token
is not kept: when the token expires, remote login again. The token is bound to the
URL and issuer it was issued for, so registering the name again for another server
(remote add --force) does not send the old token there.
Agents and CI do not use login. They bring their own token, an agent from its own
client credentials and a CI job from the OIDC token of the job, and the server maps
it to service accounts through its entitlements.