@allocator-one/operator
v1.30.1
Published
Operator from the terminal and any agent, with Harness issue tracking: the harness CLI as the operator command
Readme
@allocator-one/operator
The operator CLI and MCP server: the CLI for Harness,
allocator.one's issue tracker, under the command operator, for driving
Operator from the terminal and any agent. It is the same build and version
as @allocator-one/harness-cli (the harness command) and shares its
login, so every harness <command> below works as operator <command>.
Install
npm i -g @allocator-one/operator
operator --helpOr run it without installing:
npx -y @allocator-one/operator --helpAs an MCP server for Claude Code (it reports its name as operator):
claude mcp add --scope user operator -- npx -y @allocator-one/operator mcpoperator --help lists the Operator commands first, then the issue-tracker
commands under "Issues:". @allocator-one/harness-cli installs the harness
command; the two packages can be installed side by side.
Requires Node >= 20.
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-browserprints 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, asGET /api/v1/sessiondescribes it. On a server without that route, or with anaone_…API token, those fields arenullunder--json. With an outside expert'spxc_…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-cliis the full credential that Harness staff get.operator-cliis the Operator-only credential that any other admin or member Operator serves gets. It reaches the Operator commands,whoamiandlogout, 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_TOKENorHARNESS_ACTORin 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, thenHARNESS_TEAM, then the repo config. With none of them it is a usage error, and the claim is never sent.--all,--limit, and--pageare 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." (
nullwith--json) and exits0.A rate limit, server error, or unreadable response exits
1, and an unreachable API exits3. 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_progressbefore claiming again.
Work routed by label. When a team mixes executors (exec/codex,
exec/claude-code, exec/michael), each agent filters its own queue and
claims only its own steps:
harness ready --team FR --label exec/codex # only Codex steps
harness ready --team FR --label exec/codex --claim # claim the next one
harness ready --team FR --exclude-label exec/codex # everything else
harness blocked --team FR --label exec/codex # Codex steps waiting on a blocker--labelrepeats, and an issue must carry every label given.--exclude-labelrepeats, and drops issues carrying any of them. Both filter on the server.--claimsends the same filter with the claim, so the server never hands you an issue outside it, even when several agents claim at once. An empty filtered queue prints "Nothing to claim: team FR has no unassigned ready issues labelled exec/codex." and exits0.- Several labels or an exclusion need a server that supports label lists. The CLI checks with one read first. Against an older server it refuses rather than show an unfiltered queue or claim an issue outside the filter.
- MCP:
issue_listtakeslabelsandexclude_labels, andissue_claim_nexttakesteam,labelandexclude_labelwith the same meaning.
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. An issue that belongs to a plan gets a
Plan: line naming the plan by its root identifier, which the plan
commands take, then its status, the revision it runs, and whether that
revision lists the issue (Plan: FR-1 · released · revision 8 · not in revision,
with Operator step for a step Operator runs). --json carries the same as
plan ({id, root, status, held, revision, in_revision, executor}, or
null).
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|epicis applied as atype/<type>label.--priority urgent|high|medium|low|nonetakes names only. Bare numbers are refused because bd and Harness number priorities differently.--team <key>falls back toHARNESS_TEAMand 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--acceptanceset the remaining text fields.--project <name>files the issue in a project of the organization, and--milestone <name>in one of that project's milestones. Names ignore case; a name two projects (or milestones) share is refused, so pass the id. A milestone needs the project named with it.
Update. harness update <id> with:
--claim: atomic. It assigns you and moves the issue to the team's first started state. A conflict (exit1, HTTP 409) names its reason: someone else holds the issue, or it is not claimable yet. Some reasons come with a next step.agent_step: a person can't claim an Operator step; to do it yourself, set its executor tononewithharness plan edit <plan>and release the plan again.plan_not_released,not_in_plan_revisionandedited_since_release: when the server names the one plan governing the issue as the one to release, the error ends with the command (run harness plan release FR-1). When several plans govern it, the server names none as the one to release, and the error names the nearest to look at instead (harness plan show FR-1). A completed plan, or a server that does not name the plan, gets no such hint.waiting_on_condition:harness showlists what it waits on; tick a gate withharness 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).--project <name>moves the issue to a project (""takes it out of its project). A new project drops the old project's milestone unless--milestonenames one of the new project's.--milestone <name>alone is named within the issue's current project (""clears it).--notesreplaces the notes and--append-notesappends 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 withcreate. 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 (or --reason-file) is posted as a comment on the
issue, so put the whole summary there instead of commenting separately
first. Closing an issue a newer one supersedes cancels it and moves its blocking
edges to the successor; with no reason the server posts "Superseded by ",
and the output names the successor. Record the replacement as a relation first (harness relation add <new> --type supersedes <old>), not only in a comment: the relation is what show follows to the live issue. 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/--allare accepted but ignored). Archived issues are not searched. If the semantic side is unavailable, stderr sayssemantic 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), --project <name> and
--milestone <name> (a name within --project, or an id alone), and
--scope all|archived. harness ready and harness blocked take
--project and --milestone too, though not with ready --claim.
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 list --team IO # the team's plans, by root
harness plan show IO-120 # by its root issue, or its id
harness plan release IO-120 --collaborator [email protected]
harness gate satisfy IO-121 "Finance sign-off"
harness gate satisfy IO-121 compliance # by the gate's key
harness gate decline IO-121 compliance --reason "The NAV doesn't reconcile"Finding a plan. Every plan command takes the plan's id or its root
issue's identifier (FR-1, in any casing). A step's identifier does not
open its plan: harness show <issue> names the plan an issue belongs to by
its root. harness plan list lists a team's plans, newest first, with
their root, status, whether they are held, the revision they run (-
before the first release) and their title:
ROOT STATUS HELD REVISION TITLE
FR-1 released no 8 Format radar
FR-200 held yes - Next quarterIt takes --team (or HARNESS_TEAM or the repo config), --status
(held, released, running, completed or needs_attention) and
--limit (default 50, at most 100). There are no pages: when a list comes
back full, a stderr notice says older plans may exist. --json is the
server's list unchanged, each plan with its id, root, title,
status, held, revision, released_at and inserted_at.
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,priorityas a number from 0 for none to 4 for low,assignee,label_ids,project,milestone, …) pluskey,parent(the key of the step that contains it),blocked_by(keys),executor(none, the default, oroperator),team, andconditions. Operator steps are filed in the Operator team. Only steps without sub-steps carry an executor, edges, or conditions. projectandmilestonetake a name (ignoring case) or an id. The root's are its steps' too: a step that leavesprojectout takes the root's project, and the root's milestone unless it names its own.project: nullfiles a step in no project.- Wait conditions:
not_before(a UTC instant),gate(alabela person ticks), andevent(event,subject_id, and for a stage changetarget_id). The allowed events aredeal_stage_changed,opportunity_stage_changed,form_submitted, andinvestment_executed. Every condition must hold before the step is ready. - A gate can carry a
key, a lowercase slug (a-z,0-9and-) unique among the step's gates, whichgate satisfyaccepts.gate: <key>is shorthand forkind: gatewith that key, labelled with the key unlesslabelis given. A gate can also have anowner:role:<role>(one of the fund-team rolesgp,compliance_officer,valuer,accountant,auditorandcounsel),user:<id or email>(a member who can use Operator), orexpert:<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: nullor no owner leaves the gate unowned. - A step can carry
when, the predicates it runs on, all of which must hold: a gate decision on a step ({gate: <gate key>, step: <step key>, decision: approved | declined}) or a value in a step's output ({$from: <step key>, path: <JSON Pointer>}with one ofequals,inorexists). When one doesn't hold, the step is skipped rather than blocked.optional: truelets the steps it blocks run when it is skipped, cancelled or fails. Both travel as written; the server checks them. - A plan has at most the organization's max plan steps, its root included: 1,000 unless a Harness admin changed it in Harness settings → Plans, and never more than 2,000. Separately, it has at most 100 Operator steps, the fixed limit of the delegated grant Operator runs them under.
--team(orHARNESS_TEAMor the repo config) is the root's team and the default team for people's steps.--idempotency-keyworks as it does forcreate: 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 plan over a step cap gets too_many_steps, once for each cap it is
over, and the line says which cap it is and whether anyone can raise it
(on create the root has no identifier yet, so the line starts plan:):
FR-1: the plan has 1203 steps, above this organization's max plan steps (1000): a Harness admin can raise "Max plan steps" in Harness settings → Plans (max_plan_steps, up to 2000) [too_many_steps]
FR-1: the plan has 103 Operator steps, above the fixed limit of 100: Operator's delegated grant caps a plan's agent steps, and no setting raises it — hand some to people (executor none) or split the plan [too_many_steps]The same applies to plan edit and plan release, which check the caps
again, and to the MCP tools' error text.
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> prints the plan's status, budgets,
revision, and spend estimate, and for a plan a schedule or trigger drafted,
which one and its occurrence (Started by: schedule Weekly review
(2026-11-09T08:00:00Z), or trigger <name> (event:<id>); --json carries
it as started_by, null for a plan a person drafted), then every step in tree order (indented by
depth) with its key, executor, state, and status. The status is ready,
held (an Operator step changed since the release, which waits for the
next one; a person's changed step stays ready), not in revision (added after it),
waiting (on a wait condition), container, done, cancelled,
skipped (closed by the plan without its work), archived, or -. A
fan-out's copies sit under their fan-out step, each title prefixed with its
item's place (Item 3 · Notify the LP for the item at index 2; --json
carries it as copy, {"fanout": "<fan-out step>", "index": 2}, null for
any other step) — never the item itself or its digest:
IO-124 - action Todo ready Item 3 · Notify the LPThe edges come after the steps, then a Branching: section for the steps that
have a when, are optional or were skipped, and then each step's wait
conditions:
Branching:
IO-121 skipped: its gate was declined
IO-122 runs when gate compliance on step review is declined
IO-123 runs when fetch output /status equals "ok" · optionalA skipped step says why: its gate was declined, its when didn't hold, a
step it waits on or the container it sits in was skipped, or it was
optional and was cancelled or failed. 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 tickAn 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)).
An Approvals: section lists each action step's operation and approval:
none (a read), release (it runs on the release), confirmation or
callback (it waits on a person's approval in the app, with its gate's state
and condition id), or why it can't run (operation_unknown,
operation_not_supported, operation_not_previewable):
Approvals:
FR-2 list_lps · approval none
FR-3 send_capital_call · approval confirmation · gate open for approval · 6f1e9c2a-…A Runs: section lists each step that has run in the plan, with its
latest run's status, attempt, retries and continues used, outcome code, and
Operator job id (for harness job <id>):
Runs:
IO-121 failed · attempt 2 · 1 retry · outcome reached_max_iterations · job 0b9f3c55-6d3e-4a43-9a43-35ab3f4d1c11
IO-122 succeeded · attempt 1--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), execution (its latest run in the plan, shaped as
harness show --json gives it, or null when it hasn't run) and an action
step's approval (mode, and gate: its confirmation gate's
condition_id and state).
Edit. harness plan edit <plan> --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 personEditing a released Operator step, or handing one to a person, holds it
until the plan is released again. A person's step stays workable, and the
next release takes the change in; until then the plan lists every changed
step under Changed since the release:. 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> releases the plan as
you: it freezes the next revision, which takes in the steps added or edited
since the last one, 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> pauses every step until the plan is
released again.
Templates. harness plan save <plan> --as <name> saves a held plan's
tree as the next version of the named template: each step's key, fields,
parent, blocked_by, executor, campaign fields, wait conditions and labels,
but no budgets, assignees, projects or due dates. Every step needs a key,
and a template holds no plan step. The name is a lowercase slug (a-z,
0-9, - and _) that no built-in uses. Saving a name again adds a version;
a saved version never changes. A plan step runs a template with
plan_template_ref: <name> or <name>@<version>.
harness plan templates lists the built-ins first (read-only, with no step
counts), then the latest version of each saved template by name, a page at
a time: --limit (default 50, at most 100), and --after <name> for the
next page, which a full page names on stderr:
NAME VERSION BUILT-IN STEPS OPERATOR SAVED
capital-call 1 yes - - 2026-10-01
quarterly-letter 3 no 4 2 2026-10-30Schedules and triggers. A plan schedule drafts a held plan from a
pinned template on a cadence; in release mode it also releases it, within
its release terms. A plan trigger drafts a held plan each time an event
fires and never releases. Both run as their owner, the person who created
them, and pin the template's version when created or edited (<name> pins
its latest). The commands are the same for both:
harness plan schedule create --file schedule.yaml --name "Weekly review" --template weekly-review
harness plan schedule list # name, template, mode, state, next and last run, id
harness plan schedule show <id> # the schedule and its latest 20 runs
harness plan schedule edit <id> --timezone UTC
harness plan schedule pause <id> # resume <id> starts it again
harness plan schedule adopt <id> # Harness staff: make it yours
harness plan schedule runs <id> --limit 200
harness plan schedule delete <id> --yes
harness plan trigger create --name "Onboard on submission" --template investor_onboarding --event form_submittedcreate and edit read the API's request body from --file, as JSON or
YAML under the plan file's rules, and take flags for its scalar fields:
--name, --team (team_key), --template, and --mode, --timezone
and --business-day for a schedule, or --event, --target (target_id)
and --max-runs-per-day for a trigger. A field given both in the file and
as a flag is refused. A schedule's cadence, arguments and
release_terms, and a trigger's arguments, are structured, so they go in
the file:
mode: release
timezone: Europe/Vienna
business_day: following # a weekend occurrence runs on Monday
cadence: { kind: weekly, days: [1], hour: 9, minute: 0 }
arguments: { quarter: Q4 }
release_terms: { spend_budget_cents: 5000 }A cadence is daily, weekdays, weekly with days (ISO weekdays, 1 is
Monday), monthly with day (1 to 28, or last), or quarterly with
month (1 to 3, of the quarter) and day, at hour and minute local
time. A trigger's argument can bind an event field: {"$event":
"subject_id"}, "target_id" for an event with a target, or
"occurred_at". create files the schedule or trigger in --team, else
HARNESS_TEAM or the repo config, when the body names no team_key.
--mode, --business-day, --event, --target and --max-runs-per-day
values the API does not take exit 2 before anything is sent; the server
checks the rest (the template, cadence, time zone, bindings and the plan the
template would draft) and names every refusal.
list shows each one's state: active, paused, or paused (<reason>)
when the server paused it (owner_ineligible, repeated_failures, or a
schedule's invalid_timezone):
NAME TEMPLATE MODE STATE NEXT RUN LAST RUN ID
Weekly review weekly-review@3 draft active 2026-11-09T08:00:00Z 2026-11-02T08:00:00Z 5c4ed01e-…
Month end month-end@1 draft paused (repeated_failures) - - 8d2f…A trigger's list shows its event and the most runs it makes in a UTC day
instead. show adds the owner, cadence or event, arguments and failures,
then the latest 20 runs: each run's occurrence (a schedule's nominal time,
or event:<id>), status (pending, drafted, released, failed or
skipped), when it ran, the plan it drafted (harness plan show takes it)
and why it failed or was skipped (missed, rate_limited,
template_changed, …). runs lists up to --limit runs (default 50, at
most 200); there are no pages, and a full list says on stderr that older
runs may exist.
edit is its owner's alone, and confirms it again. pause, resume and
delete are its owner's or Harness staff's; resume checks it again as its
owner and is refused (owner_ineligible) while the owner may no longer start
plans. adopt lets Harness staff make one theirs: it runs as them from
then on, and a paused one stays paused until they resume it. delete needs
--yes, and the runs and plans it drafted stay. --json prints the
server's data unchanged, {"deleted": "<id>"} for a delete.
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. A condition
UUID writes directly without listing conditions, so a non-staff gate owner can
reach the server's write authorization. A UUID always addresses that condition,
even if another gate uses it as its key or label. Keys and labels require the
staff-only lookup; a denied lookup explains how to use a condition id instead.
When a name matches multiple gates, the command lists their ids instead of
choosing. 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, a key or label write reads the conditions again to find it. A direct condition-id write never lists conditions, including on errors, and leaves the owner unnamed if the refusal does not name one. Direct writes also make no claim about other conditions still holding the issue.
Gate decisions. harness gate decline <issue> <gate> --reason <text>
declines a gate on a step of a live plan, and the step closes as declined.
Steps whose when names the gate's decision branch on it, and the steps
the declined one blocks are skipped in turn unless it is optional.
--reason is required, up to 2,000 characters, and a decline without one
exits 2 before anything is sent. harness gate approve <issue> <gate>
ticks the gate as gate satisfy does, and takes no reason: the server keeps
reasons only for declines. Both find the gate as gate satisfy does (key,
label or condition id) and need the same standing; an owned gate's owner
alone decides it. A decision is final. A decline can't be undone, and an
approval can't be once a step's when has read it. The server refuses
another decision, or an untick, with a 409 (already_decided,
decision_final or decision_used), and the error says where to see the
decision:
harness: The gate has already been decided — a gate is decided once; harness conditions IO-121 shows its decision (HTTP 409)A gate that is not on a live plan's step can't be declined (422
not_a_plan_step).
Confirmation gates. A plan action step whose write waits on a person's
approval carries a confirmation gate (kind: confirmation). Approving it
runs the step's Infra One operation as the plan's releaser, on the exact
preview the person saw. The server lets nobody approve or decline one with
a credential, so an agent working on someone's login can't approve its own
writes: a person signed in to the app does it there. The gate commands still
find one, by its condition id or by its operation id as its name
(harness gate satisfy FR-3 send_capital_call), and send their usual request,
never a preview. The server refuses it with a 409
(confirmation_requires_session), which changes nothing, and the error
prints the gate's operation, approval mode and state, the preview it would
approve (its sequence number, summary and target), and the link to approve
it in the app. It exits 1:
harness: Cannot satisfy the confirmation gate for send_capital_call on FR-3: a plan step's confirmation gate is approved or declined only in the app, by a person signed in, never with a credential (HTTP 409)
operation: send_capital_call · approval confirmation · open for approval
preview 3 (2026-10-31T09:00:00Z): Send the Q3 capital call to 12 LPs
target: capital_call cc-42
approve or decline it in the app: https://harness.infra.one/acme/issues/FR-3The server sends the preview only to the gate's holders and the plan's
releaser. Anyone else, and everyone before the step has been previewed
(awaiting its preview), gets the line that says so instead.
gate_set and gate_decide over MCP return the same error and take no
preview.
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. A decided gate says how: [x] … (approved <when>
by <who>), or [-] … (declined <when> by <who>: <reason>). A
confirmation gate is named by its operation, with its approval mode, its
state (awaiting its preview, open for approval, approved, waiting for
its run, approved and run, declined or its preview was refused) and,
when the server sends one, its preview's summary:
[ ] confirmation gate send_capital_call owned by GP (role:gp) · approval confirmation · open for approval · preview 3: Send the Q3 capital call to 12 LPs (capital_call cc-42) · <id>harness show and harness plan show list conditions the same way.
--json is the list as the server sends it, with each gate's decision
and decision_reason, and a confirmation gate's confirmation
(operation, mode, state and preview, which is null for anyone who
may not approve it).
A server without the plan API answers the plan commands with a 404, and the error says that the server may predate plans. A root identifier that opens no plan is the same 404 as an unknown id, and the error says how to find the plan an issue belongs to.
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 idharness 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>, harness gate satisfy|unsatisfy and
harness gate approve|decline read, tick and decide 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, ticks and decides the gates it owns on them: assignments, conditions <issue> and gate satisfy|unsatisfy|approve|decline (assignment_list, condition_list, gate_set and gate_decide 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 jobslists 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.--statustakes comma-separated job states (draft,planning,ready,running,waiting_for_approval,waiting_for_human,paused,failed,needs_reconciliation,completed,cancelled) or the aliasesopen(not done),waitinganddone.--minelists 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.--allfollows every page, 50 jobs each unless--limitsays otherwise, for up to 100 pages; past that it stops, exits1and asks you to narrow the list with--statusor--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> --changesshows 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_setisnullunder--json).harness job <job-id> --peopleshows 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--jsonreturns 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'swaiting_onis set: an approval, a person, reconciliation, verification or delivery.watchstops 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, sowatchkeeps following it, every 10 seconds.--through-waitsfollows through a person's waits too, also every 10 seconds.--after <seq>starts after an event sequence instead of replaying the history.--jsonprints one document when the watch ends, with the outcome, the job's last state, the events read (ids, codes, numbers and booleans only), andnext_afterto resume from.watchonly reads, so it retries a failed poll three times (after 1, 2 and 4 seconds, or longer when the server sendsRetry-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--afterto resume from, and--jsonstill 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 (exit2). 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-filetogether 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
3like 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
1and says so; checkharness 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 2They 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).
continueopens 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, asjobdoes.procedure savesaves 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 showprints 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'sscoped_inputsrequirecontact_reconciliation, a run needs--group <contact-group-id>and--source <uploaded-file-id>.procedure runstarts 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=valuepasses 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--versionthat isn't a positive integer,--version,--params-file,--groupor--sourcegiven twice, or a--groupor--sourcewith whitespace around it exits2before 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
--jsonalike.
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:
3only when the request never reached the server (the name did not resolve, the connection was refused, the TLS handshake failed). Nothing changed.1with "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, orjobsfor a run's new job) before trying again. A 503service_unavailablefromprocedure runis 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, awrite, or asend(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 asunknown, 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:
--allfollows every page (up to 200), and--cursor <cursor>reads the next one, or every page from there with--all.tools searchtakes--domain,--all, and--cursoras well. An empty--domainor--cursoris 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.--jsonreturns the capability with itsinput_schema(orresult_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.
Code index
harness code sync # sync this worktree’s overlay
harness code sync --path ../app # use that worktree’s host/org config
harness code status --json # active version and local freshness
harness code status --skipped # include the base’s skipped-files reportThe repository must be shared through the organization’s Harness GitHub
connection and code indexing must be enabled for that org. The server builds
commit bases from GitHub. The CLI polls a fetching base with backoff, opens an
overlay sync, uploads only blobs that sync requests, and activates it. It never
uploads a base or follows source symlinks. A successful sync reports the server’s
state; queued means indexing has been requested, not that queries are ready.
Safe source reads require macOS 11 or later, or Linux with /proc/self/fd;
code sync refuse
