npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@allocator-one/harness-cli

v1.15.1

Published

CLI and MCP server for Harness, allocator.one's issue tracker

Readme

@allocator-one/harness-cli

The harness CLI and MCP server for Harness, allocator.one's issue tracker. The same CLI is also published as @allocator-one/operator, under the command operator, for driving Operator from the terminal and any agent (see Two names). It is a standalone Node client that keeps no local issue state. Issues live on the server, so there is nothing to commit or sync.

Install

npm i -g @allocator-one/harness-cli
harness ready

Or run it without installing:

npx -y @allocator-one/harness-cli ready

The same CLI under its Operator name, as the operator command:

npm i -g @allocator-one/operator
operator --help
npx -y @allocator-one/operator --help

Requires Node >= 20.

Two names: harness and operator

One build ships as two npm packages, always at the same version from the same release. Each installs one command:

| Package | Command | | --- | --- | | @allocator-one/harness-cli | harness | | @allocator-one/operator | operator |

Install either package or both. They don't conflict, and npx -y or pnpm dlx runs the package's one command.

Both names run the same code against the same server and share everything stored locally: the login in ~/.config/harness/credentials.json, the repo config, the caches and the HARNESS_* environment variables. Sign in with operator login or harness login and both commands are signed in. logout under either name signs both out.

Only the presentation depends on the name you typed:

  • operator --help lists the Operator commands first (today jobs, job, watch, steer, cancel, pause, continue, procedure, plan, gate, conditions, assignments, tools, tool, skills and mcp), then the issue-tracker commands under "Issues:", then sign-in and housekeeping under "General:". harness --help is unchanged, with one flat list of commands.
  • operator mcp serves the MCP server under the name operator, and harness mcp under harness. The tools are the same.

Messages, exit codes and --json output are the same under both names, so a script written for harness works with operator unchanged. The exceptions are the version label and the hints that tell you what to type, such as operator login: they name the command you typed, because someone who installed only @allocator-one/operator has no harness. The CLI tells the names apart by the command path it was started with. Launchers that start it by its file path instead, such as npm's Windows shims, pnpm, Volta and Yarn PnP, hide that path. In that case the CLI goes by the package that installed it, which gives the same answer.

Sign in

You don't need to configure anything. Commands target https://harness.infra.one, and the first command you run in a terminal opens a browser login and then carries on. One login covers every shell, worktree, and agent slot on the machine.

  • harness login: browser sign-in (PKCE, loopback callback on 127.0.0.1). --no-browser prints the URL and asks you to paste the token instead. --url <host> signs in to another host, and --timeout <ms> changes how long it waits for the callback (default 3 minutes). Login tokens last about 30 days.
  • harness logout: revokes the stored login on the server and deletes it locally.
  • harness whoami: shows the resolved host and where the credential came from. For a login it also shows the kind of credential, the user, the organization and when it expires, as GET /api/v1/session describes it. On a server without that route, or with an aone_… API token, those fields are null under --json. With an outside expert's pxc_… credential it names the expert's role and plan instead (see Outside experts).

A login gets one of two kinds of credential. The server picks the kind from your standing in the organization you choose:

  • harness-cli is the full credential that Harness staff get.
  • operator-cli is the Operator-only credential that any other admin or member Operator serves gets. It reaches the Operator commands, whoami and logout, and the server refuses everything else (issues, plans, gates) with 403.

login says after signing in which kind you got (kind under --json). If the new credential fails verification, nothing is stored. A credential the browser login just minted is then revoked, in one best-effort attempt, so a 30-day credential isn't left behind. A token you pasted is never revoked, whatever the failure, since you may be using it elsewhere. Holders keep their kind even after gaining staff standing: they log in again to get the full credential.

operator login, operator logout and operator whoami are the same commands and use the same stored login.

The automatic login only runs when stdout is a terminal and CI is unset or empty. Elsewhere (agents, CI, pipes), a command that has no credential prints how to authenticate and exits with code 2. For those headless contexts, mint an aone_… API token (admin → API tokens) with the harness:read scope, adding harness:write if the workflow changes anything (claim, create, update, comment, close), and export it as HARNESS_API_TOKEN. An outside expert uses their pxc_… credential the same way (see Outside experts).

Never set HARNESS_API_TOKEN or HARNESS_ACTOR in an interactive session. The browser login attributes writes to the real user, and overriding it falsifies the audit trail. Both variables are only for headless CI and agent slots.

Every identifier includes its team key, as in CLI-12 or ALL-4901. Older issues keep legacy prefixes: IO- issues were created before team-prefixed numbering, ALL- numbers up to the import ceiling came from Linear, and BD- from beads.

Daily workflow

harness ready --claim                           # claim the team's next ready, unassigned issue
harness show CLI-12                             # read it, including attached screenshots
# … do the work …
harness close CLI-12 --reason "Implemented in 1a2b3c4"

Queue. harness ready lists open, unblocked issues, highest priority first. Blocked issues, hold-labeled issues, and parents with sub-issues that are not completed are excluded. Only a completed blocker resolves: a cancelled or archived one still blocks until it is completed or the dependency is removed or re-pointed. An issue that is waiting on a wait condition (see Plans) is not ready either. harness blocked lists issues that are waiting on a blocker. Both commands take --team. ready marks agent steps, the plan steps Operator runs, with [agent] before the title and a legend under the table, and --json gives every issue an agent_step flag. Only the plan driver claims an agent step. The issue payload carries no executor, so the CLI goes by the team: agent steps are filed in the organization's Operator team (named Operator, keyed OPERATOR, or OPERATOR2 and so on). That is a heuristic. A step handed back to a person keeps its team and stays marked, and the claim itself is the authority.

Claim the next issue. harness ready --claim is a single request: the server picks the team's most important ready issue that has no assignee (urgent, high, medium, low, then none; oldest first, then by id), assigns you, and moves it to the team's first started state, all in one transaction. Concurrent callers get different issues, and the CLI does no paging, sorting, or retrying of its own.

  • It needs a team: --team, then HARNESS_TEAM, then the repo config. With none of them it is a usage error, and the claim is never sent.
  • --all, --limit, and --page are refused with --claim, because the server already ranks the whole ready set.
  • An empty queue is a normal outcome: it prints "Nothing to claim: team CLI has no unassigned ready issues." (null with --json) and exits 0.
  • A rate limit, server error, or unreadable response exits 1, and an unreachable API exits 3. None of these is reported as an empty queue. The CLI never retries the claim. When an unreadable response or a lost connection leaves it unknown whether the claim landed, the error says to check harness list --team <key> --assignee me --status in_progress before claiming again.

Read. harness show <id> prints the fields, relations, children, and comments. It also downloads the images attached to the issue's description, design, acceptance criteria, notes, and comments (up to 20, cached locally) and prints their paths. Screenshots are often the reproduction, so read them. --no-images skips the downloads. If a download fails, a stderr notice says so and the command still succeeds. harness comments <id> prints the full, untruncated comment thread with comment ids, oldest first. It requests up to 200 comments per page and follows the oldest comment id until the server reports has_more=false. --json and the MCP comment_list tool return the same complete array of full comment objects. Missing pagination metadata, repeated comment ids, or an older-page failure produce an error without presenting a partial thread as complete. The Blocked: line names a cancelled or archived blocker as such (yes, by CLI-3 (cancelled), CLI-4 (archived), CLI-5). When the issue has wait conditions, Waiting: says how many still hold it, and a Wait conditions: section lists each one ([ ] gate "Finance sign-off" · <id>, [ ] gate "Compliance sign-off" [compliance] owned by Compliance officer (role:compliance_officer) · <id>, [x] not before … (satisfied …), system event) with the key and id harness gate satisfy takes. harness activity <id> prints the issue's activity timeline: comments, creation, state and assignee changes, wait-condition events (Ada satisfied the gate "Finance sign-off"), and field changes (changed priority from High to Urgent, renamed the issue from “…” to “…”, edited the description, added label regression and removed label idea, set the parent to CLI-3, archived the issue). A description edit records only that it happened, never the text. When a write was made under an agent or CLI label, the person comes first and the label follows in quotes: Ada via “Claude Code” moved the issue from Backlog to In progress. A field this CLI does not know yet prints as changed the <field> field; --json passes every entry through as the server sent it. A server that does not record field changes yet refuses to serve them; the timeline then comes without them, and a stderr notice says so. It shows the newest 20 entries (--limit accepts 1–100), and --all reads further back.

Create. Creating an issue before you write code keeps the work tracked:

harness create --title "Fix login redirect" --type bug --priority high \
  --description "…" --parent CLI-3 --assignee me --due-date 2026-11-01 \
  --label regression --idempotency-key fix-login-redirect-1
  • --type task|bug|feature|epic is applied as a type/<type> label.
  • --priority urgent|high|medium|low|none takes names only. Bare numbers are refused because bd and Harness number priorities differently.
  • --team <key> falls back to HARNESS_TEAM and then to the repo config. With none of these set, the command is a usage error.
  • --label (repeatable) looks for a matching label in the target team first and then at workspace scope. Missing labels are created at workspace scope (which requires Harness administrator standing) before the issue is filed, and the issue is created in a single request with all its labels.
  • --idempotency-key <key> (1–200 printable ASCII characters, no leading or trailing space) makes a retry safe. If a create times out, run it again with the same key to get the original issue back instead of a duplicate. The CLI never retries a write on its own.
  • --design, --notes, and --acceptance set the remaining text fields.

Update. harness update <id> with:

  • --claim: atomic. It assigns you and moves the issue to the team's first started state. A conflict (exit 1, HTTP 409) names its reason: someone else holds the issue, or it is not claimable yet. Two reasons come with a next step: agent_step (Operator runs it; to do it yourself, set its executor to none with harness plan edit <plan-id> and release the plan again) and waiting_on_condition (harness show lists what it waits on; tick a gate with harness gate satisfy). It cannot be combined with --assignee.
  • --state <name-or-type>: moves the issue to the workflow state whose name or type matches.
  • --title, --description, --design, --acceptance, --priority, --parent, --assignee ("" unassigns), --due-date ("" clears).
  • --notes replaces the notes and --append-notes appends to them. Pass only one of the two.
  • --add-label / --remove-label (repeatable, name or id) add or remove individual labels without replacing the rest. You can only add labels that belong to the issue's team or the workspace, and a missing name is created at workspace scope, as with create. If two attached labels match a name, remove by id.

Dependencies. harness dep add <issue> <blocker>: the second argument blocks the first. harness dep rm <issue> <blocker> removes the dependency. For other relation types, see relation under Other commands.

Close. harness close <id> [<id>…] closes one or more issues as completed. Use --cancel to close them as canceled, or --duplicate-of <id> to close a single issue as a duplicate of the canonical one. The duplicate-of form records a duplicate relation and cannot be combined with --cancel. --reason records why the issue is done. The output lists the issues each closure unblocked (Closed CLI-12 — unblocked: CLI-13, CLI-14); act on them instead of leaving them in the queue. Only completing releases dependents: a canceled issue still blocks them. A duplicate close moves its blocking edges to the canonical issue, and any edge that cannot move stays on the duplicate. When the closed issue still blocks anything, an indented line names those dependents (Still blocking: CLI-13 (a cancelled issue still blocks its dependents)) so you can remove or re-point those dependencies. With several ids, each one is closed on its own. A failure prints FAILED <id>: …, and the command exits 1 after trying the rest. harness archive <id>… archives issues, --restore brings them back, and harness list --scope archived lists them.

Comment. harness comment <id> "text" [--mention <name>]… posts a comment. The server resolves each mention and notifies that person. harness comment edit <id> <comment-id> "new text" and harness comment delete <id> <comment-id> (alias rm) work on your own comments. Comment ids are UUIDs from harness comments.

Find. There are two search commands:

  • harness search <question…> searches by meaning. It returns a ranked list with an exact identifier first, followed by results ordered by relevance across a lexical and a semantic signal, so it can find issues that share none of your words. Write the question in plain language instead of keywords. Results default to 10 and are capped at 20, with no pages (--page/--all are accepted but ignored). Archived issues are not searched. If the semantic side is unavailable, stderr says semantic search unavailable — showing lexical matches only.
  • harness list --search <text> is the paginated lexical filter, which matches title or issue number.

harness list filters: --status open|in_progress|closed, --assignee <me|id|email|name|""> ("" means unassigned), --created-by <me|id>, --involved me, --priority <name>, --label <name> or --type <type> (only one of the two), --team <key>, --parent <id> (children of an epic), and --scope all|archived.

Overview. harness stats prints per-team and total counts. harness prime prints those counts, the organization's ten most important ready issues across every team (urgent first and priority none last, ranked the way harness ready --claim ranks, though that claims only unassigned issues on one team), and a short workflow reminder. It is meant for the start of an agent session. Against a server older than this read it shows the ten newest ready issues instead.

Text from a file

When text comes from somewhere other than your own typing, such as issue content, a commit message, or model output, pass a file instead of a shell argument. Quoting can be defeated: a " ends the string, $(…) substitutes, and a heredoc ends at any flush-left delimiter line the text happens to contain.

harness create --title "…" --description-file ./body.md
harness update CLI-12 --notes-file ./notes.md --acceptance-file ./ac.md
harness comment CLI-12 --body-file ./comment.md
git log -1 --format=%B | harness close CLI-12 --reason-file -

| Command | File options | | --- | --- | | create | --title-file, --description-file, --design-file, --notes-file, --acceptance-file | | update | the same five, plus --append-notes-file | | close | --reason-file | | comment, comment edit | --body-file |

Other commands (project, milestone, doc, link, and so on) take inline values only. plan create and plan edit read a whole JSON or YAML document with --file (see Plans), under the same stdin and UTF-8 rules, up to 8 MB (the API's request limit). - reads from stdin, and only one option per invocation can do that. stdin must not be a terminal. Passing both the inline and the file form of a field is a usage error, and so is a file that produces an empty title, reason, or comment. Conflicting options are caught before anything is read, so a rejected command never consumes your pipe. A file must be valid UTF-8 and at most 1 MB, and one trailing newline is dropped, as with $(cat file).

Plans

A plan is a root issue and its steps, created in one request: their parents, blocked_by dependencies, executors, teams and wait conditions. Either the whole plan is created or nothing is. Plans start held, so nothing runs until a person releases one.

harness plan create --file plan.yaml --team IO --idempotency-key q3-report
harness plan show <plan-id>
harness plan release <plan-id> --collaborator [email protected]
harness gate satisfy IO-121 "Finance sign-off"
harness gate satisfy IO-121 compliance        # by the gate's key

The plan file is the API's request body, as JSON (.json) or YAML (anything else, and stdin with --file -). YAML is read as YAML 1.2 and held to what JSON can carry: dates stay strings, and a duplicate key, an explicit tag such as !!timestamp, or .inf/.nan is refused. The root's fields sit at the top level, and each step has a key that other steps refer to:

title: Ship the Q3 LP report
spend_budget_cents: 5000        # needed to release Operator steps
steps:
  - key: gather
    title: Gather the fund data
    executor: operator          # Operator runs it; the description is its instruction
    description: Pull the Q3 NAVs for every fund in the portfolio
  - key: review
    title: Review the numbers
    team: IO                    # a person's step; defaults to --team
    blocked_by: [gather]
    conditions:
      - kind: gate
        label: Finance sign-off
      - gate: compliance          # a gate with this key; the label defaults to it
        label: Compliance sign-off
        owner: role:compliance_officer
  - key: send
    title: Send the report
    blocked_by: [review]
    conditions:
      - kind: not_before
        not_before: "2026-10-15T09:00:00Z"
  • A step takes the issue fields (title, description, priority as a number from 0 for none to 4 for low, assignee, label_ids, …) plus key, parent (the key of the step that contains it), blocked_by (keys), executor (none, the default, or operator), team, and conditions. Operator steps are filed in the Operator team. Only steps without sub-steps carry an executor, edges, or conditions.
  • Wait conditions: not_before (a UTC instant), gate (a label a person ticks), and event (event, subject_id, and for a stage change target_id). The allowed events are deal_stage_changed, opportunity_stage_changed, form_submitted, and investment_executed. Every condition must hold before the step is ready.
  • A gate can carry a key, a lowercase slug (a-z, 0-9 and -) unique among the step's gates, which gate satisfy accepts. gate: <key> is shorthand for kind: gate with that key, labelled with the key unless label is given. A gate can also have an owner: role:<role> (one of the fund-team roles gp, compliance_officer, valuer, accountant, auditor and counsel), user:<id or email> (a member who can use Operator), or expert:<email> (an outside expert, never a member's email). Only the owner can tick an owned gate. You can't name yourself as an owner, and that includes a role you hold. owner: null or no owner leaves the gate unowned.
  • A plan has at most 100 steps, its root included.
  • --team (or HARNESS_TEAM or the repo config) is the root's team and the default team for people's steps. --idempotency-key works as it does for create: if a create times out, run it again with the same key to get the same plan back.

Validation. The server checks the whole graph and an invalid plan creates nothing. The command exits 1, and stderr lists every violation by the step key it is about:

harness: The plan is not valid; nothing was changed (HTTP 422)
  step "review" (steps[1]): parent names no step of this plan, or no issue in this organization (parent: nope) [unknown_parent]
  plan: A plan with Operator steps needs a positive spend budget [spend_budget_required]

A gate's owner is checked the same way. The owner violations are invalid_owner, unknown_role, unknown_owner, owner_is_member, owner_is_actor (you named yourself), owner_not_allowed (an owner on a condition that is not a gate), expert_owner_requires_plan, and duplicate_gate_key. A gate: that conflicts with the condition's kind or key is invalid_condition.

Show. harness plan show <plan-id> prints the plan's status, budgets, revision, and spend estimate, then every step in tree order (indented by depth) with its key, executor, state, and status. The status is ready, held (edited since the release), not in revision (added after it), waiting (on a wait condition), container, done, cancelled, archived, or -. The edges and each step's wait conditions come after the steps. Each gate shows its key and owner. A Waiting for: section then lists the open gates each step waits for, with the gate's owner and the people who can tick it. When nobody can, it says nobody can tick:

Waiting for:
  IO-121  "Compliance sign-off" [compliance] · Compliance officer (role:compliance_officer) · can tick: Ada Lovelace, Grace Hopper (expert, Acme LLP)
  IO-122  "Legal review" [legal] · Bob Smith (user:7d3c2a1e-5b4f-4c8d-9e0a-1f2b3c4d5e6f) · nobody can tick
  IO-123  "Smoke test" · no owner, anyone with standing can tick

An owner shows as its name followed by how the server names it: a role (Compliance officer (role:compliance_officer)), a member by user id (Bob Smith (user:<uuid>), even when the plan document named them by email), or an outside expert by email ([email protected] (expert)).

--json prints the plan as the server sends it, including each step's waiting_for (condition_id, key, label, owner, and holders: [] when nobody can tick, null for an unowned gate or when the holders are not known).

Edit. harness plan edit <plan-id> --file edits.yaml changes step executors and keys, naming each step by its step key or issue identifier, and the budgets (retry_budget, spend_budget_cents):

steps:
  - step: gather
    executor: none              # hand the step to a person

Editing a released step holds it until the plan is released again. A budget change needs the plan held first. Descriptions, parents, and edges are edited with the issue commands (update, dep). Conditions are set when the plan is created. Editing them later goes through the API's issue condition endpoints, which have no command yet; gate satisfy ticks a gate.

Release and hold. harness plan release <plan-id> releases the plan as you: it freezes the next revision and lets ready steps run. --collaborator <id-or-email> (repeatable) adds people to the plan room where its Operator steps run, and --retry-budget and --spend-budget-cents set the budgets. Each Operator step runs as a job (harness jobs names its plan step), and the changes a job proposes need a person's approval in the app, at the job's approval link. The CLI never approves. harness plan hold <plan-id> pauses every step until the plan is released again.

Gates. harness gate satisfy <issue> <gate> ticks a gate and harness gate unsatisfy <issue> <gate> unticks it. <gate> is the gate's key, its label, or its condition id, all shown by harness show. None of the three outranks the others: when <gate> exactly matches more than one condition, say one gate's key and another gate's label, the command refuses and lists each one's id, key, and label instead of choosing. Pass the condition id of the one you mean. Only when nothing matches exactly does a label or key match ignoring case, and that too is refused when more than one gate matches. Ticking a gate requires staff standing in the organization or a place in its plan's room. The server refuses an Operator-only login (operator-cli) here, even for a person in the plan's room: it reaches only the Operator commands, whoami and logout. Only the owner can tick an owned gate, and only its owner or Harness staff can untick it. Anyone else gets an error that names the gate's owner as of the refusal, which the server sends with it, and exits 1:

harness: Cannot satisfy gate "Compliance sign-off" on IO-121: only its owner, Compliance officer (role:compliance_officer), can tick it (HTTP 403)

A gate with no owner by then gets the same error without a name. Against a server that does not send the owner, the command reads the conditions again to find it.

Conditions. harness conditions <issue> lists an issue's wait conditions, oldest first, one per line as show lists them: each one's box, what it waits for (a gate's label, key and owner), when and by whom it was satisfied, and its id. --json is the list as the server sends it.

A server without the plan API answers the plan commands with a 404, and the error says that the server may predate plans.

Outside experts

An outside expert invited to a plan can hold a pxc_… credential. The expert mints it in their own expert view, with a fresh code from their authenticator app, and it lasts at most 30 days. It reaches only:

  • their assignments (GET /api/v1/expert/assignments);
  • on a step assigned to them, the gates they own: listed, ticked and unticked.

To the CLI it is an ordinary bearer credential. In a terminal, paste it into harness login --no-browser, which verifies it against the assignments and stores it like any login. For an agent or a script, export it as HARNESS_API_TOKEN. Writes are attributed to the expert's own account either way.

harness login --no-browser                # paste the pxc_ credential
harness whoami                            # the expert's role and plan
harness assignments                       # their steps, and the gates they own on each
harness conditions IO-130                 # their gates on one step
harness gate satisfy IO-130 valuation     # tick one, by key, label or id

harness whoami says the credential is an outside expert's and names the expert's role, the plan and how many steps are assigned to them:

Host:        https://harness.infra.one (via stored login)
Credential:  stored login, an outside expert's pxc_ credential
Expert:      Valuer on plan 0b6f5e9a-6c1e-4d3f-9a39-2f1f8f7e4c11 (released) · 1 assigned step
Reaches:     its own assignments and the gates it owns on them (assignments, conditions, gate)
Verified:    ok (server API 1.4.693)

whoami --json always carries expert: null for any other credential, and for a pxc_… one {id, role, plan: {id, status} | null, assigned_steps}, beside the usual url, source, verified, server_version and host_source.

harness assignments lists each step assigned to the expert that they still hold, in identifier order: its state, due date, how many open issues block it, the gates on it they own, and its description (left out for a step Operator runs). The expert view never names a member: a gate shows its owner as a role, or you for the expert's own gate, and who ticked it as you, the role it was ticked as, an outside expert or a member. --json is the server's data unchanged: expert ({id, role}), plan ({id, status} or null) and assignments, each with identifier, title, description, state ({name, type}), due_date, open_blockers and gates. Any other credential gets a 403 (expert_credential_required).

harness conditions <issue> and harness gate satisfy|unsatisfy read and tick only the expert's own gates. After a tick the CLI counts only the expert's own gates still open, because the step's other conditions are out of the expert's view. A step that isn't assigned to the expert answers 404, which the error says.

Every other command that reads or changes Harness or Operator data is refused with a 403 expert_bound_credential, reads and writes alike. (whoami, version, login and logout keep working: they use only the routes the credential reaches.) The CLI says what the credential reaches and exits 1:

harness: This expert credential may only read its own assignments and tick its own gates — an outside expert's pxc_ credential only lists its own assignments and reads and ticks the gates it owns on them: assignments, conditions <issue> and gate satisfy|unsatisfy (assignment_list, condition_list and gate_set over MCP) (HTTP 403)

The platform reads the credential, the expert's seat and the account again on every request. An expired or revoked credential, a revoked or ended seat, a suspended account or a changed authenticator answers 401 at once. With a pxc_… credential the error lists those causes, and the command exits 1. If the seat is still open, mint a new credential in the expert view. harness logout forgets a stored pxc_… credential without revoking it, because the session route refuses it; revoke it in the expert view to end it at once. Storing a pxc_… credential replaces (and revokes) a stored member login, so log in again to use the other commands.

Operator jobs

jobs, job and watch read the Operator jobs you see in the Chat Mode workbench, in the organization your credential belongs to. They need the operator:read scope (a browser login carries it) and an admin or member role there.

harness jobs --status waiting --mine
harness job <job-id>
harness watch <job-id>

Everything is by reference: states, codes, counts, ids and workbench links. None of them shows a job's objective text, because the API never returns it: it can quote what Operator read. Each job shows its objective_kind and the workbench link where its objective can be read instead. Approval happens in the app. No command approves anything; they print the approval link.

  • harness jobs lists jobs newest first, with their state, what they wait on, kind, owner and plan step, followed by the approval link of each job waiting for approval. --status takes comma-separated job states (draft, planning, ready, running, waiting_for_approval, waiting_for_human, paused, failed, needs_reconciliation, completed, cancelled) or the aliases open (not done), waiting and done. --mine lists only your own jobs. Pages hold 20 jobs (--limit, at most 50). A page can come back short, or even empty, while older jobs exist, because the server drops the jobs you may not see after reading the page. stderr says so. --all follows every page, 50 jobs each unless --limit says otherwise, for up to 100 pages; past that it stops, exits 1 and asks you to narrow the list with --status or --mine.
  • harness job <job-id> shows the job's status, plan version, progress, remaining window and budget, spend, the change set waiting for approval with its link, the grant, verification, effect counts, blockers, artifacts, pinned skill, people, saved procedures, and what you can do in the workbench.
  • harness job <job-id> --changes shows the job's latest change set instead, by reference: its id, version, status, digest and plan version, and each action's type, version, order, effect count, required capabilities and digest, with the approval link. Values and targets never leave the app; review them there. A job that has proposed nothing prints "No pending change set" (change_set is null under --json).
  • harness job <job-id> --people shows who is on the job, by name: the requester, the execution principal (the person it runs as), the owner, the approvers and the collaborators. A person the job doesn't name shows as their user id; the API never sends emails. Pass both flags to see both, and --json returns them under the job's id, state, link and approval link.
  • harness watch <job-id> prints the job's events and state changes as they happen, polling every 2 seconds, until the job completes, fails or is cancelled, or a person has to act. A person has to act exactly when the job's waiting_on is set: an approval, a person, reconciliation, verification or delivery. watch stops there with the link to act on: the approval link when the job waits for approval. A job paused with nothing for a person to do, such as one whose deliveries are still sending, resumes by itself, so watch keeps following it, every 10 seconds. --through-waits follows through a person's waits too, also every 10 seconds. --after <seq> starts after an event sequence instead of replaying the history. --json prints one document when the watch ends, with the outcome, the job's last state, the events read (ids, codes, numbers and booleans only), and next_after to resume from.
  • watch only reads, so it retries a failed poll three times (after 1, 2 and 4 seconds, or longer when the server sends Retry-After) when the API is unreachable, answers with a 5xx, or rate-limits. If the poll still fails, or you press Ctrl-C, stderr names the --after to resume from, and --json still prints the document with the events read so far. When the reader of its output goes away (watch <id> | head -1), it stops polling and exits quietly.

watch exits 0 when the job completed, 4 when a person has to act, 5 when the job failed or was cancelled, and 130 on Ctrl-C (see Scripting contract).

A 403 names why the server refused: Operator switched off for the organization (operator_disabled), no admin or member role there (operator_not_permitted), a credential that acts as no person (actor_required), or a missing operator:read scope (forbidden). A job that does not exist, is in another organization, or is not shared with you answers the same 404.

Steering, cancelling and pausing a job

steer, cancel and pause change or stop an Operator job the way the workbench's controls do, under the same rules and with your current authority. They need the operator:write scope (a browser login carries it) and an admin or member role in the organization.

harness steer <job-id> "Only use the audited Q3 figures"
harness steer <job-id> --instruction-file instruction.md   # "-" reads stdin
harness cancel <job-id>
harness pause <job-id>
  • harness steer <job-id> <instruction> posts the instruction in the job's room, attributed to you, wherever the app offers Steer. The job's owner and collaborators may steer. Work the instruction changes needs a fresh approval: earlier approvals stop applying to it. Steering a running job stops its current run so it can take the instruction. An instruction is 1 to 10,000 characters once trimmed, and is sent trimmed; a longer or blank one is refused before anything is sent (exit 2). Quote it: unquoted words that look like options (--json, --help) are read as options, not text. Put -- before an instruction that starts with -, or pass it with --instruction-file <path> (- for stdin), which takes any text as it is. Words and --instruction-file together are a usage error.
  • harness cancel <job-id> ends the job. Effects already committed stay committed, and the command prints their counts; staged effects that never ran count as unknown.
  • harness pause <job-id> holds a running job for a person without resetting its active-time budget. Continue it to run again.

Only the job's execution principal may cancel or pause it. Each command prints the job as job shows it afterwards (--json: the same document), and approving still happens in the app.

None of them is idempotent, so the CLI sends each one once and never retries it, not even after a timeout or a server error. What a failure means depends on whether the request can have reached the server:

  • It cannot have (the name did not resolve, the connection was refused, the TLS handshake failed): nothing changed, and the command exits 3 like any unreachable API.
  • It may have (a timeout, a dropped connection, a 5xx, an answer that cannot be read): the outcome is unknown. The command exits 1 and says so; check harness job <job-id> before trying again.

The server's message comes first, followed by what to do about it:

| Status | Code | Meaning | | --- | --- | --- | | 400 | bad_request | The request carried a field the server does not accept. Nothing changed; update the CLI. | | 403 | principal_required | cancel and pause are the job's execution principal's own. Its owner or a collaborator can still steer it, which stops its current run. | | 403 | operator_disabled, operator_not_permitted, actor_required, forbidden, run_bound_credential | As for jobs, except that forbidden means a missing operator:write scope. | | 404 | not_found | The job does not exist, is in another organization, or is not shared with you. | | 409 | invalid_state | The app wouldn't offer the control now: Steer on a finished, failed or awaiting-approval job (or one needing reconciliation), Cancel on a job that already ended, Pause on a job that isn't running. | | 409 | reconciliation_required | Steer: partial effects need reconciling in the app first. | | 409 | deliveries_unsettled | Steer: a delivery is still sending, or its outcome is unknown. If job shows the job waiting on delivery, settle it in the app (an unknown delivery never settles by itself); otherwise steer again once it has sent. | | 409 | principal_inactive | The job's execution principal no longer holds the rights to run it. Nothing changed. | | 409 | cancellation_scope_changed | Cancel: the job's work changed while it was being cancelled. It was not cancelled; read it and decide again. | | 422 | invalid_instruction, validation_error | Steer: the instruction is blank or too long, or the job's room refused it. |

Continue and saved procedures

harness continue <job-id>                               # a new window for a job, as Continue does in the app
harness procedure save <job-id> --name "Monthly close"  # save a verified job as a procedure
harness procedure show <procedure-id>                   # its versions and the parameters a run takes
harness procedure run <procedure-id> --param period=2026-09 --param count:=3
harness procedure run <procedure-id> --params-file params.json --version 2

They act with the authority, budgets and kill controls of the Chat Mode workbench, and need the operator:write scope (a browser login carries it; procedure show needs operator:read).

  • continue opens a new audited window for a job that ran out of time or waits on a person, or retries a failed run or verification, exactly as Continue does in the app. Only the job's execution principal, the person it runs as, may continue it. It prints the job afterwards, as job does.
  • procedure save saves a job's recipe as a versioned procedure. Only the job's owner can save it, and only once it completed with its work verified. Saving again saves another procedure.
  • procedure show prints the procedure's name, the job it was saved from and that job's link, the current version, each version's recipe digest, capability ids and action roster, and the JSON schema of the parameters a run takes. It never shows a version's recipe or anything the saved job read. When the schema's scoped_inputs require contact_reconciliation, a run needs --group <contact-group-id> and --source <uploaded-file-id>.
  • procedure run starts a new job from the procedure, as Rerun does in the app. Nothing of the saved job carries over: the run has fresh inputs, your own current rights, and a change set that needs its own approval, which happens in the app. It prints the new job's link. --version <n> runs only while that version is still current. --param name=value passes a string and --param name:=<json> a JSON value (repeatable); --params-file <path> (- for stdin) passes one JSON object instead. A bad --param (one without =, a name given twice or with whitespace around it), an unreadable or invalid --params-file, both together, a --version that isn't a positive integer, --version, --params-file, --group or --source given twice, or a --group or --source with whitespace around it exits 2 before anything is sent.
  • When the new procedure or job can't be read back as you, the answer is only its id and link, for people and under --json alike.

None of these writes is idempotent, so none is ever retried automatically, not after a timeout or a 5xx either: a second continue opens another window, a second save saves another procedure, a second run starts another job. Their exit codes say whether trying again is safe:

  • 3 only when the request never reached the server (the name did not resolve, the connection was refused, the TLS handshake failed). Nothing changed.
  • 1 with "Outcome unknown" when it may have: a timeout, a dropped connection, an answer that could not be read (then the server received it), or a 5xx. Check what the error names first (job <id>, the job's procedures, or jobs for a run's new job) before trying again. A 503 service_unavailable from procedure run is the exception: the server refused the run before starting it.

The MCP tools follow the same rule: only a request that never reached the server says "cannot reach the API".

A continue that answers 404 may still have happened, if the job went out of your sight just as it was continued: check job <id> first. job <id> --changes reads the job and then its change set; when a new change set lands between the two reads, stderr says so.

Every refusal exits 1 with the server's message, what it means and the HTTP status:

| Status | Code | Meaning | | --- | --- | --- | | 400 | bad_request | The request carried a field the server does not accept. Nothing changed; update the CLI. | | 403 | principal_required | continue: only the job's execution principal may continue it. | | 403 | owner_required | procedure save: only the job's owner can save it. | | 403 | operator_disabled, operator_not_permitted, actor_required, forbidden | As for every Operator command; forbidden here means a missing operator:write scope. | | 404 | not_found | The same answer for a job or procedure that doesn't exist, is in another organization, or that you can't see. | | 409 | invalid_state, reconciliation_required, principal_inactive, deliveries_unsettled | continue: nothing to continue, partial effects to reconcile in the app, a principal who lost the rights, or a delivery still sending or of unknown outcome (settle it in the app when job <id> shows it waiting on delivery). | | 409 | version_conflict, concurrent_change | procedure run: the named version is no longer current, or the procedure changed while the run started. No job was started. | | 422 | not_saveable, validation_error | procedure save: the job isn't completed and verified, or the name holds control characters. | | 422 | invalid_rerun | procedure run: the inputs don't fit the parameters, or your current rights don't cover the run. No job was started. | | 503 | service_unavailable | procedure run: Operator's operational profile is unavailable. No job was started. |

Operator's catalog

What Operator can do in your organization, and the skills it has:

harness tools                                   # the capabilities Operator can use now, by id
harness tools --domain crm                      # one domain's
harness tools domains                           # every domain, with how many
harness tools search "find a party"             # by keyword, best match first
harness tool crm.party.search                   # one capability, with its whole input schema
harness tool crm.party.search --schema result   # a read's result schema
harness skills                                  # the skills assigned to Operator
  • Only what Operator can use now. A capability that ships disabled or is switched off for the organization never appears, so there is no "enabled" column.
  • Kinds. Each capability is a read, a write, or a send (a write delivered beyond the platform, such as an email). Write and send capabilities, and any proposal, always need a person's approval in the app before they take effect. A capability without a kind shows as unknown, never as a read. These commands run and approve nothing.
  • Pages. The index comes 25 to a page and a search 15, with no page size to choose. When there is more, stderr says how many there are in all and names the next page's cursor: --all follows every page (up to 200), and --cursor <cursor> reads the next one, or every page from there with --all. tools search takes --domain, --all, and --cursor as well. An empty --domain or --cursor is a usage error.
  • Schemas. tool <id> fetches every page of the schema and prints it whole. A page that is out of order, skipped, of another schema, or at odds with the others' fragment count fails the command, and no schema is printed. --json returns the capability with its input_schema (or result_schema) as an object. Only a read has a result schema.
  • Skills show each skill's invocation name, title, the SHA-256 of its content, and its id. Their content and description are never exposed.

Operator needs the operator:read scope, an admin or member role in the organization the credential is bound to, and Operator switched on there. Login tokens carry the scope; aone_ tokens need to be minted with it. A refusal exits 1 with the server's message and what it means (operator_disabled, operator_not_permitted, actor_required). tool answers a capability that doesn't exist, ships disabled, or is switched off with the same 404.

Uploads

harness upload <path> uploads a file and prints a markdown reference to paste into a description or comment. --json returns the URL, filename, content type, byte size, and markdown instead.

  • Images (PNG, JPG, WebP, GIF) can be up to 25 MB.
  • Videos must be .mp4 files of up to 100 MB, with H.264 8-bit 4:2:0 video, optional AAC audio, and optional mov_text subtitles. The server validates the media and does not transcode other formats. Harness shows linked videos as an inline player.

A new video is private to its uploader until that user saves its link in an issue description or comment. After that it follows the access policy of the issue that holds it, and reusing the link elsewhere doesn't change who owns it. harness show downloads images only, never videos. The older attach-image <path> command accepts images only (25 MB).

Other commands

| Command | Notes | | --- | --- | | relation list <issue> | Every typed relation, with relation ids. | | relation add <issue> <other> --type <t> | blocks, blocked_by, related, duplicate, duplicate_of, duplicated_by. | | relation update <issue> <relation-id-or-issue> --type <t> | blocks, blocked_by, related, duplicate_of. | | relation rm <issue> <relation-id-or-issue> | If more than one relation matches the other issue, pass the relation id. | | link list\|add\|update\|rm <issue> … | URLs attached to an issue. add <issue> <url> [--title]; update <issue> <link-id-or-url> --url … \| --title … refuses an ambiguous URL; rm <issue> <link-id-or-url> removes the first match. | | project list\|show\|create\|update | list [--search] [--state] (paginated). create --name … [--description] [--summary] [--state] [--lead <user-id>] [--start-date] [--target-date]. | | milestone list\|create --project <id> | create … --name … [--description] [--target-date]. | | doc list\|show\|create\|update | list [--search] (paginated). create --title … [--content] [--project <id>]. | | label list\|create\|update\|delete | create <name> [--color], update <id> [--name] [--color], delete <id> --yes (refused while issues carry the label). | | team list\|create\|rename\|set-default | create --key <KEY> --name … sets up the default workflow states. The key is permanent; rename <key> --name … changes only the display name. set-default <key> makes the team the org's default filing target. | | state list\|add\|update\|order\|delete | A team's workflow states (--team, else HARNESS_TEAM or the repo config). add --name … --type backlog\|unstarted\|started\|completed\|canceled [--color], order <id,id,…> (every state id, in order), delete <id> --yes. states is an alias of state list. | | member list\|add\|set-role | The org's tracker roster. add --email … --role admin\|staff\|collaborator, set-role <user-id> --role …\|none (none revokes). | | user list | Users, for resolving assignees. | | version | CLI and server API versions. Works before you log in, and so does harness --version. |

team rename, team set-default, label update, label delete, and the write commands of state and member require Harness administrator standing, and so does member list.

Provider keys

Admins store the org's provider API keys (Anthropic, OpenAI, Google, OpenRouter, Brave Search, Perplexity) in Harness under Settings → Model keys. harness keys passes them to tools without ever printing them:

  • harness keys list shows the provider slugs the org has keys for and the environment variable each one maps to (--json returns {providers: [{provider, env}]}). It never prints the values.
  • harness keys exec [--only <slugs>] [--override] -- <command> [args…] runs the command with the keys in its environment and exits with the command's exit code (128 + the signal number if the command was killed by a signal). --only brave,perplexity passes just those providers and also removes your shell's own copies of the other provider variables, so the command sees only what it needs. If the org has no key for a provider you requested, your shell's variable is kept and stderr says so. Slugs are trimmed and case-insensitive. An --only list that names nothing (--only ,) is a usage error, not a request for everything. Without --override, a variable that is already set in your shell wins (a blank variable counts as unset). --json is accepted but has no effect, because stdout belongs to the command. A SIGTERM sent to harness is forwarded to the command. Ctrl+C reaches the command directly from the terminal, and harness waits to report its exit. Works on macOS and Linux only.

Each slug maps to the same variable as in rcl: anthropic → ANTHROPIC_API_KEY, openai → OPENAI_API_KEY, google → GEMINI_API_KEY, openrouter → OPENROUTER_API_KEY, brave → BRAVE_API_KEY, perplexity → PERPLEXITY_API_KEY. Reading keys requires the model-keys:read scope. Login tokens have it; aone_ tokens need to be minted with it. There is deliberately no MCP tool for keys, because a tool result is text the model can read.

Scripting contract

  • --json on every command except mcp. It replaces the human output with the response data as JSON. keys exec accepts it but changes nothing: its stdout is the child command's output.

  • stdout carries data only, so it is safe to pipe. Diagnostics go to stderr:

    • → https://harness.infra.one on every command that writes, naming the host it sent the write to.
    • harness: showing 50 of 132 (page 1 of 3) — pass --all for the rest, or --page to step through. when a paginated read did not return everything, and harness: --limit N was capped at M by the server. when the server reduced the page size you asked for.
    • The activity, search, and show notices described above.
    • The update notice (see Staying current).
  • Pagination. ready, blocked, list, project list, and doc list return 50 records per page by default (ready --claim has no pages). --limit changes the page size, --page selects a page, and --all follows every page (pass --all or --page, not both). The truncation notice goes to stderr only, so a script that reads just the --json output cannot tell a full page from a complete result. Pass --all when you need everything.

  • Exit codes. keys exec returns the command's exit code instead.

    | Code | Meaning | | --- | --- | | 0 | Success, including ready --claim finding nothing to claim. | | 1 | The command failed. Causes include an API error (the message gives the HTTP status, followed by any per-field errors or plan violations, one per line), a response the CLI cannot interpret, a partial failure across several ids, or a local refusal such as an unreadable or oversized upload. | | 2 | Usage or configuration error, including "not signed in" outside a terminal. Nothing was sent. | | 3 | The API was unreachable (DNS, TLS, connection refused, or timeout). Retry with backoff, and if the API stays down, say so instead of dropping tracked work. Operator's writes (steer, cancel, pause, continue, procedure save, procedure run) exit 3 only when the request never reached the server; when it may have, they exit 1 with "Outcome unknown", and you check what the error names (job, or jobs for a run's new job) before running one again. | | 4 | watch only: a person has to act on the Operator job (its waiting_on is set: an approval, a person, reconciliation, verification or delivery). The link to act on is on stdout. | | 5 | watch only: the Operator job failed or was cancelled. | | 130 | watch only: interrupted with Ctrl-C. stderr names the --after to resume from. |

MCP server

harness mcp runs an MCP server over stdio. To register it with Claude Code:

claude mcp add --scope user harness -- npx -y @allocator-one/harness-cli mcp

Or register it under its Operator name. The server then reports its name as operator, and it serves the same tools:

claude mcp add --scope user operator -- npx -y @allocator-one/operator mcp

Register one of the two, not both, or every tool appears twice.

With a global install, harness mcp (or operator mcp) works as the command instead of the npx form. Any other MCP client can use the same command:

{ "mcpServers": { "harness": { "command": "npx", "args": ["-y", "@allocator-one/harness-cli", "mcp"] } } }

The server exposes 72 tools:

| Area | Tools | | --- | --- | | Issues | issue_list, issue_search, issue_show, activity_list, issue_create, issue_update, issue_claim, issue_close, issue_archive | | Comments | issue_comment, issue_comment_edit, issue_comment_delete, comment_list | | Relations | relation_add, relation_rm (blocking shorthand: blocker blocks issue), relation_list, relation_create, relation_update, relation_delete | | Links | link_list, link_add, link_update, link_rm | | Projects and docs | project_list, project_show, project_create, project_update, milestone_list, milestone_create, document_list, document_show, document_create, document_update | | Labels | label_list, label_create, label_update, label_delete | | Teams and states | team_list, team_create, team_rename, team_set_default, workflow_states, state_add, state_update, state_order, state_delete | | People | member_list, member_add, member_set_role, user_list | | Plans and gates | plan_create, plan_show, plan_edit, plan_release, plan_hold, gate_set, condition_list | | Outside experts | assignment_list | | Operator jobs | jobs_list, job_show, job_changes, job_start, job_steer, job_cancel, job_pause | | Continue and procedures | job_continue, procedure_save, procedure_show, procedure_run | | Operator's catalog | tools_search (without query it browses the index; domain, cursor, all), skills_list | | Other | stats, upload |

The tools cover the issue workflow and the admin surface. Some commands are CLI-only: keys (deliberately), login/logout/whoami, version, and prime. Read-only tools carry readOnlyHint. Each result is compact JSON of the response data. Any stderr notice the CLI would print, such as truncation, is added as a second text item. A partial failure across several ids is returned as an error, with the details for each id in the JSON. Some tools behave differently from their commands:

  • issue_list with ready: true or blocked: true accepts every list filter. The CLI's ready and blocked take only --team.
  • issue_list only lists. Claiming the team's next issue (ready --claim) is CLI-only; over MCP, claim a specific issue with issue_claim.
  • issue_show downloads images only when you pass images: true.
  • state_delete and label_delete don't need the CLI's --yes.
  • plan_create takes the plan as an object (plan), with team and idempotency_key beside it, instead of a file. Its schema describes the steps and conditions, including a gate's key and owner and the gate: <key> shorthand, which makes kind optional. Keys inside plan that the schema does not know are still sent, so the server reports them. plan_edit takes steps, retry_budget, and spend_budget_cents directly. As with every tool, a top-level argument the tool does not declare is dropped. gate_set takes issue, gate (a key, label, or condition id), and satisfied.
  • An API error's details come back in the tool error text: a validation error's fields, and an invalid plan's violations, one per line by step key.
  • With an outside expert's pxc_… credential, assignment_list, condition_list and gate_set work on the expert's own steps and gates. Every other tool returns the expert_bound_credential refusal, with the same explanation the CLI prints.
  • issue_list with ready: true gives every issue an agent_step flag, read from its team as ready does.
  • jobs_list takes status as a list of job states and aliases. There is no watch tool: poll job_show until state is completed, failed or cancelled, or waiting_on is set. A person has to act exactly when waiting_on is set; a job paused without it resumes by itself. Neither tool approves anything; give a person the approval_url.
  • job_start is the CLI's run: outcome, and optionally budget (US dollars; only the organization's admins) and skill; there is no team. It returns job_id, state, workbench_url and, when a change set waits, approval_url, and never approves: give a person the link. Each call starts another job, so it is never retried; after an "Outcome unknown" error look with jobs_list (mine) before calling again. It carries idempotentHint: false and destructiveHint: false. The endpoint needs a server that has the Operator start-job API.
  • job_steer, job_cancel and job_pause return the job as job_show does. None is idempotent, so none is retried: after an error that leaves the outcome unknown, read the job with job_show before calling one again. All three carry idempotentHint: false. job_cancel sets destructiveHint: true; job_steer (it voids earlier approvals and stops a run) and job_pause (it stops a run) leave MCP's default, which is also destructive. Approving stays in the app.
  • job_changes is the CLI's job --changes. There is no tool for job --people: job_show already returns the requester, execution principal, participants and approvers.
  • procedure_run takes parameters as an object, beside version, group_id and source_resource_id. job_continue, procedure_save and procedure_run are never retried and carry idempotentHint: false; a failed call's error says what to check before calling again, and only a request that never reached the server says "cannot reach the API".

For example, issue_list with {"ready": true, "team": "CLI"} returns the ready queue, and issue_claim with {"id": "CLI-12"} claims an issue atomically. issue_close takes ids, reason, cancel, and duplicate_of, and issue_create takes an idempotency_key for safe retries.

The MCP server resolves auth exactly as the CLI does, on every call, so a login completed while the server is running takes effect without a restart. It never opens a browser. Without a credential, a tool returns an error that tells the agent to run harness login in a terminal or set HARNESS_API_TOKEN.

Configuration

Repo config

A committed .harness-cli/config.json, found by walking up from the working directory the way git finds .git, gives every clone of a repo the same defaults:

{ "team": "CLI" }

| Field | Effect | | --- | --- | | team | Default team key. Precedence: --team, then HARNESS_TEAM, then the repo config. | | url | Target host. HARNESS_API_URL takes precedence. Before an automatic login opens a browser at a host that only the repo names, the CLI asks for confirmation, once per host. | | org | Reserved. It is parsed but has no effect yet. |

A malformed file is ignored with a warning on stderr.

Hosts and tokens

The host comes from HARNESS_API_URL, then the repo config's url, then the host of the stored login, then https://harness.infra.one. Tokens are bound to the host that issued them. A stored login is only sent to the host that issued it. HARNESS_API_TOKEN is only sent to HARNESS_API_URL, or to the default host when that variable is unset. It is never sent to the stored login's host, and when a repo config names another host, the command refuses with exit code 2 instead of sending the token there. Set HARNESS_API_URL explicitly if you do mean that host. Without HARNESS_API_TOKEN, a repo-named host needs a login of its own, and the automatic login asks for confirmation first, as described above. Plain http:// is accepted only for localhost.

Environment variables

| Variable | Effect | | --- | --- | | HARNESS_API_URL | Target host (https, or http against localhost only). | | HARNESS_API_TOKEN | An aone_… API token, or an outside expert's pxc_… credential. It takes precedence over the stored login. Headless use only. | | HARNESS_ACTOR | Write attribution, sent only when set. Without it the server attributes writes to the signed-in user, or for aone_ tokens to the token's name. Headless use only. | | HARNESS_TEAM | Default team key for commands that take --team. | | HARNESS_CACHE_DIR | Where show stores downloaded images. Must be an absolute path. | | HARNESS_NO_UPDATE_CHECK | Set to a value other than 0, false, or no to turn off the update notice and the automatic upgrade. | | HARNESS_AUTO_UPGRADE=0 | Keeps the notice but never installs anything (false and no also work). | | CI | Any value other than empty, 0, false, or no turns off the update notice and automatic upgrade. Separately, any non-empty value, including 0, stops the automatic browser login. | | XDG_CONFIG_HOME, XDG_CACHE_HOME | Used instead of ~/.config and ~/.cache when set to absolute paths. |

Files

| Path | Contents | | --- | --- | | ~/.config/harness/credentials.json | The stored login: one {url, token} pair, mode 0600. | | ~/.config/harness/trusted-hosts.json | Repo-config hosts you agreed to log