Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fe406fd
docs(deploy): two-phase deploy_component, revert_component, stage/act…
dawsontoth Jul 21, 2026
acbb3af
docs(deploy): address review — category links to #components, label s…
dawsontoth Jul 21, 2026
76bae5e
Merge branch 'main' into claude/two-phase-deploy-docs
dawsontoth Jul 29, 2026
4da3f9b
docs: add_ssh_key server-side keygen, payload retention count, status…
dawsontoth Jul 29, 2026
eb5437c
docs(cli): point the ops-table links at the sections that actually do…
dawsontoth Jul 29, 2026
40f0e69
docs(deploy): by-reference deploys, sealed credentials, CI token auth
dawsontoth Jul 29, 2026
9415237
docs: correct deployment status buckets, rolling-restart response, re…
dawsontoth Jul 30, 2026
f9ad59b
docs(deploy): add_ssh_key generate no longer needs ssh-keygen on the …
dawsontoth Aug 12, 2026
d000da2
docs(deploy): revert_component is addressed, idempotent, and config-l…
dawsontoth Aug 12, 2026
99a0f57
Merge claude/cli-token-auth-docs (main + split-out v5.2.0 token docs)
dawsontoth Aug 17, 2026
d66bcae
docs(deploy): move the deploy surface to v5.3.0 and badge it
dawsontoth Aug 17, 2026
c6f5d85
docs(deploy): address the review findings on two-phase deploy and revert
dawsontoth Aug 17, 2026
95db83f
docs(security): OIDC trusted publishing — deploy from CI with no stor…
dawsontoth Aug 17, 2026
ffc0361
docs(release-notes): add the 5.3 page covering the deploy and OIDC su…
dawsontoth Aug 17, 2026
627d8c8
docs(deploy): resync deploy setup=true with harper#1851 after review
dawsontoth Aug 17, 2026
0bbae3c
Merge branch 'claude/cli-token-auth-docs' into claude/two-phase-deplo…
dawsontoth Aug 17, 2026
4d1084d
docs: correct credential precedence, revert force, and barrier opt-outs
dawsontoth Aug 18, 2026
e1da667
docs(agents): derive version badges from core tags, not the branch pa…
dawsontoth Aug 18, 2026
5d4ce0c
docs: resync OIDC with harper#2173's token scoping, and qualify eight…
dawsontoth Aug 18, 2026
e44cf28
docs: correct the activate-existing rebuild path and four narrower cl…
dawsontoth Aug 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Prefer plain ASCII characters in Markdown unless a typographic character is genu

- Tag minor-version availability inline: `<VersionBadge version="vX.Y.0" />` for new surface, `<VersionBadge type="changed" version="vX.Y.0" />` for behavior changes to existing surface.
- Derive the version from the core release the change ships in, stripping prerelease suffixes (`5.1.0-beta.1` → `v5.1.0`).
- **Determine that release from the core repo's git tags, not from the feature branch's `package.json`.** A branch reading `5.2.0-beta.3` says which release was open when the branch started, not which one the change lands in — if a release is cut before the feature merges, the badge is silently wrong. Check `git tag --sort=-creatordate` for the newest release, and `git tag --contains <merge-commit>` for whether the change is in one; a merged-but-untagged feature ships in the _next_ version, which may be a minor bump. Re-check on every refresh pass of a long-lived docs PR, because a release cut between passes invalidates a badge that was correct when written.
- Each minor release gets a file under `release-notes/<major-codename>/` (e.g. `release-notes/v5-lincoln/5.1.md`); the sidebar picks it up automatically.
- Absolute links from `release-notes/` (or `learn/`) into current reference docs use the versioned path `/reference/v5/...` — the reference plugin maps the current version to the `v5` URL path.
- When documenting a change from a core/pro PR, cross-link the feature PR and the docs PR in both descriptions.
Expand Down
45 changes: 42 additions & 3 deletions reference/cli/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ For remote Operations API commands, the CLI uses the first complete authenticati
4. Legacy `CLI_TARGET_USERNAME` and `CLI_TARGET_PASSWORD` environment variables
5. `HARPER_CLI_OPERATION_TOKEN` and `HARPER_CLI_REFRESH_TOKEN` environment variables, or their legacy `CLI_TARGET_` equivalents — see [Token credentials for CI/CD](#token-credentials-for-cicd)
6. A token saved by `harper login`
7. `username=` and `password=` operation parameters (legacy fallback)
7. A [workload identity token](#workload-identity-oidc) exchanged with the runtime's OIDC provider (v5.3.0)
8. `username=` and `password=` operation parameters (legacy fallback)

Credentials are resolved as a pair and are never combined across sources. An incomplete pair supplied with dedicated authentication parameters or in the target URL causes the command to fail. An incomplete environment-variable pair is skipped with a warning so that a saved login token can still be used.

Entries 5 and 6 authenticate with a bearer token and apply to **remote targets only**. A local operation goes over the domain socket, which the server already trusts, so token environment variables are deliberately ignored there — a token minted for one instance would otherwise be attached to every local `harper` command in that shell and rejected.
Entries 5 through 7 authenticate with a bearer token and apply to **remote targets only**. A local operation goes over the domain socket, which the server already trusts, so token environment variables are deliberately ignored there — a token minted for one instance would otherwise be attached to every local `harper` command in that shell and rejected.

Before v5.2.0, `username=` and `password=` operation parameters took precedence over environment variables and saved login tokens. This could authenticate an operation as the wrong user when those fields were part of the operation payload, such as the user being created by `add_user`.

Expand Down Expand Up @@ -208,7 +209,7 @@ Expose the two values to the deploy step and no other credentials are needed:

**Refresh behavior.** The CLI mints an operation token from the refresh token when none is supplied, and again whenever the supplied one has expired. A token refreshed from an environment variable is held in memory for that invocation only — nothing is written to `~/.harperdb/credentials.json`, because there is no file entry for an environment-supplied credential. If the refresh token itself is rejected, the command reports that and exits non-zero rather than falling back to another identity.

**A blank token variable is an error, not a fallback.** If a namespace is set but empty — the usual shape of a misconfigured CI secret — the CLI says so and falls back to saved login credentials. It does not silently run as whoever last logged in on that machine.
**A blank token variable is reported, then skipped.** If a namespace is set but empty — the usual shape of a misconfigured CI secret — the CLI warns and continues down the precedence list, so the run proceeds under the saved `harper login` token if that machine has one. The warning is the only thing separating this from silently deploying as whoever last logged in, so treat it as a failure signal in CI rather than assuming a blank secret stops the run.

**Lifetimes.** Operation tokens expire after `authentication.operationTokenTimeout` (default `1d`) and refresh tokens after `authentication.refreshTokenTimeout` (default `30d`). The pipeline needs a new refresh token when that window closes.

Expand All @@ -218,6 +219,44 @@ Expose the two values to the deploy step and no other credentials are needed:
Create a **dedicated CI user** and run `harper login --for-ci` as that user. That scopes the pipeline's permissions to what it actually needs, and lets you revoke its access without disturbing anyone else.
:::

##### Workload identity (OIDC)

<VersionBadge version="v5.3.0" />

On a runner that can prove its own identity, the CLI needs **no stored credential at all**. It asks the runtime for an identity token addressed to your instance and trades it for a one-hour operation token. Nothing durable is stored in your CI provider, and there is no 30-day token to rotate.

Configure the instance to trust the workflow once with [`add_oidc_trust`](../operations-api/operations.md#add_oidc_trust), then grant the token permission in the workflow:

```yaml
permissions:
id-token: write
contents: read
environment: production
steps:
- run: harper deploy by_ref=true restart=true replicated=true
env:
HARPER_CLI_TARGET: ${{ vars.HARPER_CLI_TARGET }} # a var, not a secret
```

`HARPER_CLI_TARGET` is the only variable the step needs, and it is not sensitive — hence `vars` rather than `secrets`.

**This ranks below every configured credential**, and above only the legacy `username=`/`password=` payload fallback. Adding `id-token: write` to a workflow that still sets `HARPER_CLI_REFRESH_TOKEN` does not change which identity deploys; the stored token keeps winning. That is deliberate — enabling a new capability should not silently re-point an existing pipeline at a different user. Remove the secret when you want the exchange to take over.

Note the one case where the exchange _does_ take over: because it outranks the legacy fallback, a command that passes `username=` and `password=` as operation parameters on a runner with no configured credential authenticates as the trust policy's user, not as the pair in the payload. That is the intended reading of those fields — for `add_user` they describe the user being created — but it means a script relying on them as credentials changes identity the moment a policy matches.

The exchange is attempted only when the runtime actually offers an identity. GitHub Actions sets `ACTIONS_ID_TOKEN_REQUEST_URL` and `ACTIONS_ID_TOKEN_REQUEST_TOKEN` together on a job that declares `id-token: write`; without both, the CLI falls through to its other credential sources rather than reporting a failure. An unrecognized runtime falls through the same way.

On success the CLI prints, to stderr, which policy authenticated it and as whom:

```
Requesting a GitHub Actions identity token for https://my-instance.harperdb.io:9925/...
Authenticated as 'ci-deploy' via OIDC trust policy 'my-app-prod'.
```

If Harper rejects the token, the CLI reports that and carries on down the precedence list rather than aborting. Usually nothing is left, so the operation returns 401 — but if the command also passes `username=` and `password=`, the legacy fallback applies them and the operation **succeeds as that user instead**. A policy mismatch can therefore look like a working deploy under the wrong identity, so don't leave a payload credential pair on a command you expect the exchange to authenticate.

The server deliberately does not report which check failed — see [`exchange_oidc_token`](../operations-api/operations.md#exchange_oidc_token) — so diagnose with `list_oidc_trust` and the instance's `oidc-trust` log.

#### Method 3: Dedicated Authentication Parameters

<VersionBadge version="v5.2.0" />
Expand Down
Loading