vike
TraderReferenceCli

Node status

Ask a running vike-tradehub node what it is doing — `vike-cli trade status`, one read-only observe-scoped command, which replaced the retired top-level `strategy-status` verb.

`strategy-status` is retired — the command is `vike-cli trade status`

There is no alias and no named refusal. The binary does not know the word strategy-status, so typing it is an unknown-verb error that names no replacement — unlike sweep, walkforward and study, which are refused by name with their successor printed. If you arrived here from a bookmark, the command you want is vike-cli trade status --node HOST:PORT.

Ruling 17 folded the verb into the trade surface, which already holds an Observe connection to the node, and merged the REPL's old state read into it at the same time. state is gone — from the REPL and from vike-cli trade alike — because it was a READ and a WRITE on one word: state printed the mode, state halted halted the daemon, and the only thing between them was a second token. halt and resume are their own verbs now, named for what they do, and this one read answers both former questions. Most of what the old page described survived the move; the frame changed and the output gained the trading mode.

trade status is the shell-level answer to what is that daemon actually doing? — the trading mode and the mounted-strategy registry, in one output. It is read-only: it authenticates under the observe scope and there is no spelling of it that writes.

The wire verb existed before the command did. Until this module the only way to ask a running node what it was running from a shell was to compile a throwaway probe — the gap the I10 rehearsal found, and the problem this command was built to solve.

Two reads, because no single wire verb answers both

The registry comes from vike_tradehub_client::strategy_status (Request::StrategyStatus) and the mode from vike_tradehub_client::snapshot_once (Request::Snapshot). Each is its own short-lived vike_tradehub_client::Scope::Observe connection, registry first, mode second — crates/vike-cli/src/cmd/trade_status.rs's run performs them in that fixed order. Neither frame carries the other's fields, and merging them would mean a NODE_PROTO_VERSION bump, which breaks the handshake against every running node, to save one round trip on a command a human types.

The registry read decides the exit code. It does not decide what is printed. Both halves are rendered as an answer or a reason there is not one, never as an absence:

  • the mode read failed after the registry answered → the registry still prints, and the mode line (or, under --json, a trading_state_error key) says why it is missing;
  • the registry read failed on a node that answered — one too old for the verb, or one refusing it — → the mode read is attempted anyway and printed, because Request::Snapshot is a base verb of the protocol that every node has always served. crates/vike-cli/src/cmd/trade_status.rs's mode_read_worth_attempting carries the two kinds it deliberately does not retry under.

The exit stays the registry's, so a wrapper cannot read a partial answer as a whole one.

Why the mode half is worth two connections

Until 2026-09-10 a registry failure returned before the mode read ran, so trade status against a node too old for the strategy-verbs capability printed no trading mode at all — the operator asked a halted node what it was doing and was told only to upgrade it. "Is this node halted" must not be a question you can ask and get silence to.

Usage

vike-cli trade status --node 10.0.0.5:9200
vike-cli trade status --node 10.0.0.5:9200 --json
flagmeaning
--node HOST:PORTthe node's observe address. Required — there is no default node to ask
--jsonthe node's answer as JSON instead of the table
-h, --helpthe usage text, on stdout, exit 0

Both spellings of the valued flag parse — --node 10.0.0.5:9200 and --node=10.0.0.5:9200 — and a token beginning -- is a flag, never a value, so a dangling --node is a named usage error rather than an address of --json. That rule is crates/vike-cli/src/cmd/args.rs's is_flag_token, spelled once for every subcommand because without it a valued flag ate whatever followed it, including another flag. --json is a bare boolean and rejects an inline value (--json=1 → --json takes no value). An unknown argument is rejected by name. See global flags.

A bare word is a usage error, never a write: trade status halted is rejected as an unknown argument, because the grammar takes flags only. That is the whole of ruling 17 expressed in the parser — nothing that changes the daemon's mode may be spelled by adding a word to something that does not. crates/vike-cli/src/cmd/trade_status.rs's parse is where it is enforced, and a_bare_mode_word_is_a_usage_error_and_never_a_write pins it for halted, reducing, active and state alike.

One credential, and it is the observe one

Both wire verbs are read-only, so the node answers them under either scope, and both client functions always connect under Scope::Observe. The command therefore needs exactly one key: VIKE_TRADEHUB_OBSERVE_KEY, resolved by the dispatcher the same way trade and mcp resolve theirs — the process environment first, then <project>/settings/node.env, the same node-key store the daemon itself reads. crates/vike-cli/src/cmd/nodekeys.rs's resolve argues that precedence: an export is ephemeral, narrow and deliberate, while the store is the box's standing configuration.

That same function trims whichever value it picks, because the server's vike_tradehub_client::auth::from_vars trims too, and the agreement is load-bearing rather than tidiness: an untrimmed client signs with secret\n against a server verifying secret, and the mismatch comes back as an opaque auth refusal instead of your key file has a trailing space.

A box whose pair is still in the older <project>/settings/secrets.env keeps authenticating — the fallback is a migration, not a deprecation that breaks a running deployment — and is told once, on stderr, which file it read and which file the pair belongs in now. vike-cli backend setup writes the new one. Nothing about the migration reaches stdout, which --json makes a document.

A control key cannot stand in for an observe key

The node verifies each scope against its own key, so "has any key" would be the wrong question here. crates/vike-cli/src/cmd/trade_status.rs's run asks for the observe half specifically, and the absent-key message is the observe-specific NodeKeyring::observe_absent_message — it names the variable, both places it was looked for, and the store path a key would go in. No connection is attempted without a key, so nothing about the address is reported.

Output

Human form: the trading mode, a daemon identity line, the daemon's effective-params line, a blank line, then one aligned row per mount. Column widths come from the data, headers included, so a long strategy name cannot shear the params column. The mode cell is uppercase exactly when it matters — LIVE shouts, paper is the quiet default. The rendering is pure and pinned on a two-mount fixture, one paper row and one live:

trading state: Active
node hub-a — paper — build vike-tradehub 0.1.0 (abc1234, clean)
params: spread=0.01 size=20

  STRATEGY      MODE   PARAMS
  spread_maker  paper  venue=polymarket symbol=TOK-A spread=0.01
  np            LIVE   venue=polymarket symbol=TOK-B edge=0.02

The mode is first because an operator asking what a node is doing reads the kill switch before the roster, not after it. trading state: is Active, Reducing or Halted, and when the core sits in safe state a second line follows it:

  FAULT (core halted safe-state): fold panic in engine 0

That block is crates/vike-cli/src/cmd/trade_status.rs's mode_lines, which sits in this command's own module rather than in crate::cmd::trade, because three surfaces render it — this verb, the REPL's status, and the REPL's snapshot — and they may not word a kill switch differently. Before ruling 17 there were two spellings of it and they already disagreed.

--json

The payload the node sent is nested verbatim under strategy_status, with the mode fields beside it. The nesting is the point: a machine consumer gets the wire schema, which cannot drift from the node's, rather than a second hand-maintained shape. The two mode keys have no wire struct of their own to be verbatim about. The document is pretty-printed — crates/vike-cli/src/cmd/trade_status.rs's json_body ends in serde_json::to_string_pretty — so it spans lines: read it with a parser, not with a line filter.

keypresent whenvalue
trading_statethe mode read answered"Active", "Reducing" or "Halted"
faultthe mode read answeredthe safe-state fault string, or null
trading_state_errorthe mode read failed, or was not attemptedthe reason, as a string
strategy_statusthe registry read answeredthe vike_tradehub_client::wire::WireStrategyStatus payload — identity, effective_params, mounts
strategy_status_errorthe registry read failedthe reason, as a string

A half that could not be read is a key, never an omission. No mode is invented — trading_state and fault are genuinely absent when the read failed — and trading_state_error is what stops the document reading as this node has no mode. The earlier shape dropped the mode keys in silence, on the argument that a consumer requiring trading_state would fail loudly; that holds for a strict typed deserializer and for nothing else. jq .trading_state answers null for an absent key exactly as it does for a null one, serde_json::Value indexing returns null for both, and a human sees a document with one fewer line.

A mode read that was made and failed puts its reason on stderr as well, in both output modes, because a --json stdout is a document a script parses, not a transcript anyone reads. A mode read that was never attempted gets no line of its own: crates/vike-cli/src/cmd/trade_status.rs's run guards that stderr line on a read that actually happened, and the reason it was skipped — the registry failure — is already on stderr immediately above, where repeating it would bury it. The JSON key is there either way.

The three registry failures, and what each one tells you

crates/vike-cli/src/cmd/trade_status.rs's failure_lines is pure, so each wording is pinned by a unit test rather than needing a downgraded daemon to reproduce.

conditionwhat is printed on stderr
io::ErrorKind::Unsupportedthe feature-negotiation refusal: the node's Welcome advertised no strategy-verbs capability. The client's own sentence names the missing capability and says nothing was sent; the added line is the action — upgrade that node's vike-tradehub and re-run
io::ErrorKind::PermissionDeniedthe observe handshake itself was refused. Points at the key, not the verb, and names vike-cli secrets path as the way to see which store this side read it from
anything elsean honest transport-shaped report naming the address

There is no fourth arm, and one case is worth naming because the exit table below does separate it: io::ErrorKind::InvalidData — the node answered and refused the verb, which is what a publisher with no identity block sends — lands in that last row and prints the same cannot query line as an unreachable socket. Where it is singled out is the rung (crates/vike-cli/src/cmd/trade_status.rs's failure_exit) and the retry (crates/vike-cli/src/cmd/trade_status.rs's mode_read_worth_attempting), never the wording. Do not go hunting a distinct InvalidData message on a live box.

Every one of those lines names the verb as the operator typed it, from a single constant, so a message can never point back at the retired top-level spelling.

The old-node refusal is client-side: the verb is feature-negotiated through crates/vike-tradehub-client/src/proto.rs's FEATURE_STRATEGY_VERBS, and against a node that does not advertise it the client sends nothing after the handshake. An older node's serde cannot decode the frame, so sending would produce an opaque decode error at the node instead of a diagnosis here. The end-to-end test scripts exactly that node and asserts the connection carried no frame at all.

Exit codes

The rule is did the node answer. Everything a reachable box can tell you is a permanent configuration fact, and a wrapper that retries one of those retries it forever; only a socket that never got an answer is worth backing off and trying again. crates/vike-cli/src/cmd/trade_status.rs's failure_exit decides the rung over the same ErrorKind match that decides the words, so the two cannot come to disagree about which failure is which.

outcomeexit
the registry read answered0 — including when the mode read failed; that half is best-effort
the node answered and refused: too old (Unsupported), observe key rejected (PermissionDenied), node-side error or unexpected response (InvalidData)1
no answer came back: connection refused, unroutable, timed out, aborted3, the connect rung
no observe key resolved anywhere1
usage error — an unknown argument, a bare word, a missing --node, --json=12, the usage rung, on stderr
-h / --help0, on stdout

Those last two rows are decided in one place for the whole binary — crates/vike-cli/src/cmd/args.rs's exit_for_parse_error, which turns the help short-circuit into a success on stdout and puts everything the parser rejected on the usage rung rather than the run-failure one — and crates/vike-cli/tests/help_cli.rs runs the real binary to assert the status, the stream, and that no internal token leaks. The rungs themselves are crates/vike-cli/src/exit.rs's Exit, and the split is what a wrapper branches on: a 2 is yours to fix, a 3 is worth waiting on, a 1 means it ran and failed. A misspelled flag and a node that answered and refused are deliberately not the same rung.

What is covered

The grammar, every renderer and both halves of the failure mapping — the words and the rung — are pure and unit-tested in the module itself. The connections are crates/vike-cli/tests/trade_status_cli.rs, which drives the shipped binary against a real loopback node with no credentials, no venue and no external network:

  • trade_status_renders_the_mode_the_node_identity_and_mount_rows — both wire reads, the mode above an identity line and one row per mount, and nothing on stderr on the happy path;
  • trade_status_json_nests_the_wire_payload_beside_the_mode — the machine surface parses and carries the node's own field names;
  • an_identity_less_node_is_an_honest_error — the server-error path exits 1, never 3, and the mode still comes back;
  • an_old_node_is_refused_client_side_but_still_answers_the_mode — a scripted node lacking strategy-verbs: an actionable message, no frame sent on that connection, and the mode asked for and printed on a second one;
  • a_failed_mode_read_is_a_json_key_and_a_stderr_line — the other direction, a node that answers the registry and refuses the snapshot;
  • with_no_observe_key_anywhere_the_message_names_both_sources and legacy_keys_still_authenticate_and_say_so — the absent-key message, and the secrets.env box that must keep working and must be told where the pair belongs now.

See also

  • vike-cli trade — the surface this verb lives in. halt and resume are the verbs that change the mode, and they authenticate with the CONTROL key (VIKE_TRADEHUB_CONTROL_KEY), not this read's observe one. halt and resume are the verbs that change the mode
  • The node protocol — the keys, the frames, the handshake
  • vike-cli secrets — where the key was read from
  • vike-tradehub — the daemon on the other end

On this page