| title | CLI behavior |
|---|---|
| description | Smart defaults, caches, filtering, idempotency, error exits, prompts, and connection lifecycle that huly-cli silently applies across every command. |
Everything the CLI silently applies for you — auto-creations, smart defaults, caching, filtering semantics, error mappings. Lookup tables first, narrative second.
- Auto-creations
- Smart defaults (values the CLI fills for you)
- Ref resolution order (how flag values resolve)
- Auto-coercion in
--set key=value - Cache & index behavior
- Timeouts
- Filtering & matching semantics
- Idempotency
- Error messages include next-step hints
- Pagination
- Confirmation prompts
- Connection pooling
The CLI silently creates things on your behalf to keep common flows
one-liners. Most can be disabled with --minimal /
HULY_OPINIONATED=0.
| Command | What gets auto-created | When |
|---|---|---|
huly document create |
A General teamspace (type space-type:default, members [], description "Default teamspace (auto-created)") |
Workspace has zero teamspaces. |
huly issue create |
5 default IssueStatus records (Backlog, To do, In progress, Done, Canceled) in core:space:Model |
Workspace has zero IssueStatus. |
huly issue create |
First ProjectToDo (classic projects only) |
--assignee is non-empty (explicitly set, or defaulted to the current user by the opinionated default) AND status category is ToDo/Active. An explicit --assignee '' suppresses the cascade. See Platform behavior — Issues. |
huly dm create --person <email> / huly dm send --person <email> |
A DM with that person (resolves via resolvePersonId) |
No existing DM with that person. |
huly issue label add <ref> --label <name> |
A TagElement in tags:space:Tag (first TagCategory) |
Label doesn't exist yet. |
huly project create |
The current user is added as members: [<uuid>] |
Always (security invariant — required by SpaceSecurityMiddleware so the creator can findAll their own project). Not gated by --minimal or HULY_OPINIONATED=0. |
huly calendar create |
A new Calendar doc |
Always; --type public|private defaults to public. |
huly action create |
If --attached-to omitted, the task is attached to the owner's Person (or current user) |
Default. |
huly issue createre-tries the auto-seed on the second call if the first failed silently (model-load race). If the issue create keeps failing on a fresh workspace, run any other issue-list command first to nudge the model.
| Command | Flag | Default |
|---|---|---|
huly project create |
--sequence |
0 |
huly project create |
--members |
[<current-user-uuid>] |
huly project create |
--description |
'' (omitted with --minimal / HULY_OPINIONATED=0 — but an explicit --description '' is still preserved verbatim; only a fully omitted flag is removed from the payload) |
huly project create |
type |
tracker:ids:ClassingProjectType (the classic tracker ProjectType — note the server-side typo "Classing"). Without this default, projects may not be classic and miss the issue↔action cascade. Pass HULY_OPINIONATED=0 or --minimal to skip. |
huly issue create |
--status |
Lowest-rank IssueStatus in category ToDo (usually To do). Pin to Backlog with --status Backlog. With HULY_OPINIONATED=0 or --minimal, falls back to the lowest-rank status overall (usually Backlog). |
huly issue create |
--assignee |
Current user's email (resolved from getAccount().fullSocialIds). Pass --assignee <other> to override, --assignee '' to leave unassigned. Disabled with HULY_OPINIONATED=0 or --minimal. |
huly issue create |
--priority |
Normal if it exists in the workspace; else first priority; else omitted |
huly issue create |
--task-type |
First available TaskType for the project; if none, falls back to tracker:taskTypes:Issue (NOT tracker:issue:default — that ref is invalid and the create errors) |
huly issue create |
parent |
null (top-level), unless --minimal / HULY_OPINIONATED=0 |
huly issue create |
space |
project._id (unless --minimal / HULY_OPINIONATED=0) |
huly card create |
--card-space |
First available, non-archived CardSpace (resolved with findAll({ archived: false }, { sort: { createdOn: 1 }, limit: 1 }) — the oldest by createdOn). Falls back to literal card:space:Default only when zero CardSpaces exist. With HULY_OPINIONATED=0 or --minimal, the literal card:space:Default is used directly (which often does not exist). |
huly calendar create-calendar |
--access |
public (one of owner / team / public) |
huly calendar create-calendar |
--private |
false |
huly schedule create |
--duration |
30 (minutes) |
huly schedule create |
--interval |
15 (minutes) |
huly action create |
--priority |
NoPriority |
huly action create |
--visibility |
public |
huly action create |
--owner |
Current user |
huly action create |
--attached-to-class |
contact:class:Person |
huly action create |
--due |
none (dueDate: null) |
huly action create |
doneOn |
null |
huly action create |
rank |
0|aaaaa: |
huly time log |
--date |
Date.now() |
huly time log |
value conversion | minutes → man-hours (value = minutes/60); rounds to nearest 15 min |
huly teamspace create |
--type |
public |
--minimal / HULY_OPINIONATED=0 removes every default that is not
a security-invariant (the only one the CLI refuses to drop is the
current-user-as-member on project create).
When you pass a value to a flag like --assignee, --project,
--owner, --person, --calendar, etc., the CLI tries in this
order:
me/""(empty string) — resolves to current user. Exception:huly issue create --assignee ''treats''as "leave unassigned" (setsdata.assignee = null), suppressing the ProjectToDo cascade. This is the documented way to override the opinionated default that would otherwise auto-assign to you.- Raw
_id(matches^[a-z-]+:[a-z-]+:[0-9a-f-]{36}$) — used as-is. - Prefixed form (
PREFIX-123, e.g.TSK-1,USR-42) — looked up via the index. - Bare number (
42) — uses$HULY_PROJECTenv var for project context. identifier | name | label | title(lowercased) — exact match against the index.- Substring fallback (loose
includes()match) for--assigneeonly. NOT applied to--owner— see step 6b. May produce false positives; pass an exact email/name to avoid.- 6b.
--owneris exact-match only —resolveEmployeeIddoes a strict===comparison againstPerson.nameandPerson.email(if the field is populated). There is no fuzzy fallback. Pass the full name or email.
- 6b.
- Account lookup —
accountClient.findPersonBySocialKeyfor--person; falls back to workspace-localPersonscan. - Single-other-member heuristic —
resolvePersonIdin DM/Channel code picks the only other workspace member if exactly one exists. Documented for awareness; avoid relying on it.
Heads up: the substring fallback in step 6 is silently enabled for
--assigneeonly. If you pass--assignee boband there's aBob Andersonand aBob Bishop, the first alphabetical match wins. Use exact email to disambiguate.--ownerdoes NOT have this fallback — it requires an exact name or email match.
huly project update --set key=value (and huly issue update --set) coerce values automatically:
key=null→ clears the field (sendsTxUpdateDocwithoperations[key]: null)key=true/key=false→ booleankey=<numeric string>→Numberkey=<anything else>→ string
Reserved keys (silently stripped): set, unset, json, ci,
markdown, dryRun, minimal, yes, workspace, url,
defaultProjectIdentifier.
| Cache | Lifetime | Invalidation |
|---|---|---|
Resolver index (PlatformClient → Map<classId, Map<key, _id>>, backed by a WeakMap) |
In-memory, no TTL; dies with the PlatformClient |
Explicit invalidateIndex(client, classId) after every write. |
Account _accounts URL cache |
In-memory, per-host | Never invalidated; restart the CLI process to refresh. |
${XDG_CONFIG_HOME:-$HOME/.config}/huly/credentials.json (account + workspace tokens) |
On disk, mode 0600, no expiry | Refreshed on re-login. Delete the file to reset. |
${XDG_CONFIG_HOME:-$HOME/.config}/huly/active-workspace |
On disk, mode 0600 | Updated on huly workspace use <name> or --workspace. |
${XDG_CONFIG_HOME:-$HOME/.config}/huly/active-account |
On disk, mode 0600 | One line per host, updated on login. |
Stale-cache gotcha: the resolver index never expires. If someone deletes or renames a project between two CLI commands in the same shell, the second command may still see the old name. Restart the CLI process (or run any write against the changed resource) to force a refresh.
Cross-workspace safety: because the cache is keyed on the
PlatformClientinstance (WeakMap), switching workspaces — even within the same process — gives you a fresh cache automatically. No risk of stale entries bleeding across workspaces.
See Environment — Auth caching for the on-disk cache details.
| Path | Timeout | Fallback |
|---|---|---|
client.fetchMarkup (all --markdown reads) |
5 seconds | '(body fetch timed out)' |
ws raw command |
60 seconds | Promise rejects |
ws raw command ping |
5 seconds (interval) | --no-ping disables |
retry() helper (defined, unused) |
429 only |
500 * attempt² ms backoff, max 3 attempts |
There is no WebSocket auto-reconnect in the CLI. Each command
opens a fresh WS, runs, and closes in finally. If the connection
drops mid-call, the error bubbles up. See
Environment — WebSocket session reconnect
for the server-side window when a workspace is being upgraded.
| Flag | Match type | Case-sensitive? |
|---|---|---|
--status (issue) |
Exact label/name | No |
--status-category |
Strips task:statusCategory: prefix, exact |
No |
--priority (issue) |
Exact label/name | No |
--task-type (issue) |
Exact label/name OR raw _id |
No |
--role (member) |
Aliases (owner|admin|guest|docguest|readonlyguest|maintainer) |
No |
--priority (todo) |
Strict enum (High|Medium|Low|NoPriority|Urgent), throws on invalid |
No |
--visibility (todo) |
Strict enum (public|busy|private) |
No |
--archived (channel/document) |
Anything that isn't 'false' or '0' → true |
n/a |
--completed (todo) |
true|false|all |
n/a |
--description-search / --content-search / --title (todo) |
MongoDB-style regex with $options: 'i' |
No |
--private (channel) |
Non-strict coercion (anything not 'false'/'0' → true) |
n/a |
| Command | Behavior |
|---|---|
huly issue create |
If the create returns duplicate / exists / already, the CLI re-runs the lookup and returns the existing issue's _id (idempotent). |
huly project create |
Pre-flight findAll({identifier}); on already exists|duplicate|exists error, repeats the lookup and returns the existing project. |
| Error | Hint |
|---|---|
PLATFORM_NOT_FOUND / not found |
"check the ref or run huly <resource> list" |
PLATFORM_UNAUTHORIZED / 401 |
"run huly login or set HULY_TOKEN" |
PLATFORM_FORBIDDEN / 403 |
"insufficient permissions" |
PLATFORM_ALREADY_EXISTS |
mapped to ExitCode.Conflict(6) |
PLATFORM_RATE_LIMITED / 429 |
mapped to ExitCode.RateLimited(5) |
PLATFORM_VALIDATION / 400 |
mapped to ExitCode.Validation(4) |
>=500 |
mapped to ExitCode.Server(7) |
Bad --status |
lists all available statuses |
Bad --priority |
lists available priorities |
ref-resolver NotFound |
shows first 10 candidates |
See Usage — Exit codes for the full exit table.
The CLI loads the full result set in one findAll call, then slices
in-memory with --limit / --offset. There is no server-side
pagination. For very large workspaces (>10k docs of a type), prefer
filtering by project/space/date to bound the result set before
piping to jq. See
Environment — Large lists and fulltext.
Required for:
workspace createworkspace delete(active workspace also needs--force)- Any delete of ≥2 refs (
issue delete,project delete,channel delete,document delete,teamspace delete,action delete,comment delete,time delete,calendar delete,card delete,card-space delete,thread delete,channel message delete,action unscheduleof multiple slots).
NOT required for:
dm create --person(auto-creates a DM silently)dm send --person(auto-creates a DM silently)action unscheduleof a single slot- All single-ref deletes
None. Every CLI invocation opens a fresh PlatformClient /
AccountClient and closes it in finally. The SDK keeps a single
WS open for the duration of the client. This is fast (sub-second
per command) but means you cannot pipeline multiple mutations over
one WS.