Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,21 +375,27 @@ verifying what `hyp init` will install.

## Troubleshooting

`hyp status` is the entry point for any "is HypAware working?" question.
It prints the active config path, daemon install/run state, active
plugins, source and sink rows, per-client attach state, retention
window, cache size, and a recent-error count. Pass `--json` for the
stable machine-readable shape that smokes and support tools rely on:
`hyp status` is the entry point for any "is HypAware working?" question. By
default it prints a fixed-shape summary: whether the install is healthy, the
daemon's state, what is being captured, when rows last arrived, and where
they go, then a list of anything that needs you with repair commands you can
run directly. `--full` adds the inventory this summary elides: config path,
active plugins, source/sink rosters, per-client attach state, the
client-action ledger, and remote-config state. `--json` prints the stable
machine-readable shape that smokes and support tools rely on:

```sh
hyp status
hyp status --full
hyp status --json
```

When something is wrong, `hyp status` surfaces a `diagnostics:` section
with one row per finding. Each row carries a `kind` (a stable
machine-readable identifier) and one or more `repair:` lines you can
run directly. The common Phase 8 conditions:
When something is wrong, `hyp status` surfaces it in an attention section
below the summary, one line per finding, each carrying a `warning` / `error`
/ `note` severity, its message, and one or more repair commands you can run
directly. `hyp status --full` and `hyp status --json` also carry each
finding's `kind` (a stable machine-readable identifier). The common
Phase 8 conditions:

| kind | meaning | repair |
|---------------------------------------|------------------------------------------------------------------------------------|-------------------------------------------------------------------------|
Expand Down
39 changes: 21 additions & 18 deletions docs/ACCEPTANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ installed from the package under test, and a working `~/.codex`.
```sh
hyp attach codex
grep -n 'model_providers.hypaware' "${CODEX_HOME:-$HOME/.codex}/config.toml"
hyp status
hyp status --full
```

`hyp status` must show `codex [configured, attached]` and a running
`hyp status --full` must show `codex [configured, attached]` and a running
daemon. If the gateway is not running, capture cannot happen and the rest
of this procedure is meaningless.

Expand Down Expand Up @@ -82,14 +82,16 @@ installed from the package under test, and a working `~/.codex`.
hyp status --json | grep -A 6 recent_entrypoints
```

Pass condition: a `recent clients:` line naming the same `entrypoint`
string you just observed, with an age of a few minutes. This is read from
the running daemon's `status.json`, not from the cache
Pass condition: the `activity` row names `codex/<entrypoint>` with the same
`entrypoint` string you just observed, at an age of a few minutes. This is
read from the running daemon's `status.json`, not from the cache
([LLP 0164](../llp/0164-status-names-recent-clients-from-gateway-entrypoints.decision.md)),
so two things follow and both are expected, not failures: a daemon that
has been restarted since the conversation shows nothing here (the rows are
still in the cache - step 4 is the durable check), and the list is bounded
to what this daemon process has seen.
still in the cache - step 4 is the durable check), and the row is capped to
the three most recent surfaces this daemon process has seen
([LLP 0212](../llp/0212-status-is-a-triage-summary.decision.md)); use
`hyp status --full` for the complete, uncapped list.

5. Confirm the backfill route independently. The rollout tree is shared by
Codex CLI and Codex Desktop, so the session from step 3 must also be
Expand Down Expand Up @@ -149,20 +151,21 @@ installed from the package under test, and a working `~/.codex`.

### If it fails

- No rows at all in step 4: check `hyp status` for `client_attach_missing`
- No rows at all in step 4: check `hyp status` for a warning that codex
settings show no HypAware marker (`client_attach_missing` in `--json`)
or a stopped daemon, and confirm you fully quit Desktop rather than closing
its window.
- Rows arrive but `entrypoint` is null: Codex sent no `originator` header on
that route. Capture still worked; attribution did not. File that as its own
issue with the observed request path, and do not paper over it by matching
on `client_name` alone. `hyp status`'s `recent clients:` line will also be
missing the Desktop entry, for the same one reason: it counts `entrypoint`
values and invents nothing for a row that has none.
- The query in step 4 finds Desktop rows but `hyp status` names no recent
client: the daemon that captured them has since restarted (the tracker is
in-memory and daemon-scoped by design), or the gateway wrote no status
refresh before it exited. Re-run step 3 against the current daemon before
filing anything.
on `client_name` alone. `hyp status`'s `activity` row will also be missing
the Desktop entry, for the same one reason: it counts `entrypoint` values
and invents nothing for a row that has none.
- The query in step 4 finds Desktop rows but `hyp status`'s `activity` row
names nothing recent: the daemon that captured them has since restarted
(the tracker is in-memory and daemon-scoped by design), or the gateway
wrote no status refresh before it exited. Re-run step 3 against the current
daemon before filing anything.
- Step 5 finds no rollout for the session: Desktop wrote its history
somewhere other than `$CODEX_HOME/sessions`. That would invalidate
[LLP 0141](../llp/0141-codex-desktop-rides-the-codex-adapter.decision.md)'s
Expand Down Expand Up @@ -263,11 +266,11 @@ procedure checks, R11 in particular), [LLP 0172](../llp/0172-openclaw-two-lane-c
Then confirm the write itself and the daemon's view of it:

```sh
hyp status
hyp status --full
jq '.models.providers | {anthropic, openai}' "${OPENCLAW_HOME:-$HOME/.openclaw}/openclaw.json"
```

Pass condition: `hyp status` shows a running daemon and
Pass condition: `hyp status --full` shows a running daemon and
`openclaw [configured, attached]` among the clients, with no
`client_attach_missing` diagnostic (this is the PR #553 re-confirmation:
a probe-less `openclaw` used to be stuck reading as `attach n/a`
Expand Down
26 changes: 24 additions & 2 deletions hypaware-core/smoke/flows/local_only_export_withhold.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,9 +329,31 @@ export async function run({ harness, expect }) {
env: process.env,
})
expect.that('status: hyp status exited 0', textCode, (v) => v === 0)
// The default screen, not `--full`: a withheld folder has to reach the
// surface a human lands on (LLP 0212 #never-silent), and there it rides
// the `data` row beside what the cache holds and where it goes.
//
// Compared against the frame and the wrapping flattened out: the summary
// lays itself out to the terminal it is printed on, so which line a
// phrase lands on is the renderer's business, not this assertion's.
const flattened = textStdout.text().replace(/[│╭╮╰╯]/g, ' ').replace(/\s+/g, ' ')
expect.that(
'status: text output reports withholding 1 directory',
textStdout.text(),
'status: the default screen reports 1 withheld folder',
flattened,
(v) => v.includes('1 folder withheld') && v.includes('stays on this machine')
)

const fullStdout = makeBuf()
await dispatch(['status', '--full'], {
stdout: fullStdout,
stderr: makeBuf(),
kernel,
registry,
env: process.env,
})
expect.that(
'status: --full still spells the withholding out in full',
fullStdout.text(),
(v) => v.includes('local-only:') && v.includes('withholding 1 directories from forwarding (recorded locally)')
)

Expand Down
71 changes: 66 additions & 5 deletions hypaware-core/smoke/flows/status_diagnostics.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,10 @@ export async function run({ harness, expect }) {

const okStdout = makeBuf()
const okStderr = makeBuf()
const okExit = await dispatch(['status'], {
// `--full`: the inventory surface. The default screen is a summary that
// elides plugin and sink rosters by design (LLP 0212), and the rosters are
// what this case is about.
const okExit = await dispatch(['status', '--full'], {
stdout: okStdout,
stderr: okStderr,
kernel,
Expand Down Expand Up @@ -303,11 +306,11 @@ export async function run({ harness, expect }) {
)
)

/* ---------- Case 4: broken config text rendering ---------- */
/* ---------- Case 4: broken config text rendering (--full) ---------- */

const badTextStdout = makeBuf()
const badTextStderr = makeBuf()
const badTextExit = await dispatch(['status'], {
const badTextExit = await dispatch(['status', '--full'], {
stdout: badTextStdout,
stderr: badTextStderr,
kernel,
Expand All @@ -332,6 +335,54 @@ export async function run({ harness, expect }) {
(v) => v.includes('repair: hyp attach --client claude')
)

/* ---------- Case 5: the default screen is the summary ---------- */

// The surface a human actually lands on (LLP 0212). A broken install has
// to reach them here, not only under a flag: the health word, the problem
// in a sentence, and a runnable repair - and none of the inventory the
// summary exists to drop.
const summaryStdout = makeBuf()
const summaryStderr = makeBuf()
const summaryExit = await dispatch(['status'], {
stdout: summaryStdout,
stderr: summaryStderr,
kernel,
registry,
env: smokeEnv({ harness, hypConfig: badConfigPath }),
})
expect.that('summary: hyp status exited 0', summaryExit, (v) => v === 0)
// Frame and wrapping flattened out: the summary lays itself out to the
// terminal it is printed on, so which line a phrase lands on is the
// renderer's business, not this assertion's.
const summaryText = summaryStdout.text().replace(/[│╭╮╰╯]/g, ' ').replace(/\s+/g, ' ')
for (const expected of [
'HypAware',
'degraded',
'daemon',
'capture',
'activity',
'data',
"client plugin '@hypaware/claude' is enabled",
'→ hyp attach --client claude',
'hyp status --full',
]) {
expect.that(
`summary: stdout carries '${expected}'`,
summaryText,
(v) => v.includes(expected)
)
}
expect.that(
'summary: the plugin roster is not on the default screen',
summaryText,
(v) => !v.includes('active plugins:')
)
expect.that(
'summary: the diagnostic kind stays a --json/--full identifier',
summaryText,
(v) => !v.includes('client_without_gateway')
)

await obs.shutdown()

/* ---------- Span assertions ---------- */
Expand All @@ -341,9 +392,19 @@ export async function run({ harness, expect }) {
(/** @type {any} */ t) => t.name === 'status.render'
)
expect.that(
'traces: at least four status.render spans (case 1 text + json + case 3 + case 4)',
'traces: at least five status.render spans (case 1 full + json + case 3 + case 4 + case 5 summary)',
statusSpans,
(v) => Array.isArray(v) && v.length >= 5
)
expect.that(
'traces: the default screen records format=summary',
statusSpans,
(v) => Array.isArray(v) && v.some((/** @type {any} */ s) => s.attributes?.format === 'summary')
)
expect.that(
'traces: --full records format=full',
statusSpans,
(v) => Array.isArray(v) && v.length >= 4
(v) => Array.isArray(v) && v.some((/** @type {any} */ s) => s.attributes?.format === 'full')
)
// Healthy case attribute contract.
const okSpan = statusSpans.find(
Expand Down
3 changes: 2 additions & 1 deletion llp/0031-layered-config.decision.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
**Author:** Phil / Claude
**Date:** 2026-06-16
**Related:** LLP 0003, LLP 0004, LLP 0009, LLP 0010, LLP 0011, LLP 0013, LLP 0014, LLP 0017, LLP 0025; hypaware-server LLP 0009 (out of tree, design authority)
**Extended-by:** [LLP 0212](./0212-status-is-a-triage-summary.decision.md) (§Status provenance below: the per-entry tags and the convergence block move to `hyp status --full` and `--json`; the default summary states that the machine is centrally managed and lists dropped local entries, which are the two facts a reader acts on. The merge model, the layer files, and the drop logging are unchanged)

> A joined gateway boots an **effective config that is the merge of two
> layers**: a server-owned **central** layer (authoritative, locked) and a
Expand Down Expand Up @@ -312,7 +313,7 @@ edits with the code" rule:
- `init` overwrite guard (`prepareLocalConfigWrite` in `schema.js`; the
`walkthrough.js` and `core_commands.js` write sites) →
`@ref LLP 0031#local-layer-writers [implements]`.
- Status provenance (`status.js` collector, `renderStatusText`/`renderStatusJson`) →
- Status provenance (`status.js` collector, `renderStatusFull`/`renderStatusJson`) →
`@ref LLP 0031#status-provenance [implements]`.

Local-writer **collision warning** at `attach` time
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
**Author:** Phil / Claude
**Date:** 2026-07-31
**Related:** LLP 0003, LLP 0017, LLP 0086, LLP 0114, LLP 0130, LLP 0131, LLP 0133, LLP 0141
**Extended-by:** [LLP 0212](./0212-status-is-a-triage-summary.decision.md) (the rendering only: the recent-clients list is promoted from a trailing section into the summary's `activity` row, top entries first, and an install that has recorded nothing says so there; the tracker, the status-file shape, and `--json` are unchanged)

> Closes the one consequence [LLP 0141](./0141-codex-desktop-rides-the-codex-adapter.decision.md)
> left deliberately open: `hyp status` could not say "Codex Desktop traffic
Expand Down
1 change: 1 addition & 0 deletions llp/0186-reconciler-refused-marker.design.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
**Systems:** Config, Daemon
**Generated-by:** neutral
**Related:** LLP 0036, LLP 0041, LLP 0086, LLP 0109, LLP 0184
**Extended-by:** [LLP 0212](./0212-status-is-a-triage-summary.decision.md) (§`hyp status` attention-needed surface below: `refused` and `failed` are promoted into the summary's attention section with the same reason and re-arm hint; `done` and `pending` entries render only under `hyp status --full`, the ledger they belong to)

> [LLP 0184](./0184-reconciler-retries-permanent-failures.issue.md) reports
> that the action reconciler ([LLP 0036](./0036-central-config-driven-client-actions.decision.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
**Author:** Brendan / Claude
**Date:** 2026-08-04
**Related:** LLP 0132 (superseded), LLP 0070 (export seam), LLP 0031 (layered config), LLP 0100/0101 (first-sync review window), LLP 0135 (export-seam design, extended), LLP 0120 (hermes rows), LLP 0147 (CLI-backend attribution), LLP 0175 (live-capture misattribution, open)
**Extended-by:** [LLP 0212](./0212-status-is-a-triage-summary.decision.md) (the `hyp status` split line only: the summary always says where rows go on its `data` row and marks each local-only client inline on `capture` (an opted-out picker with no row of its own is stated there by count), rather than printing both lists unconditionally; the full split stays on `hyp status --full` and `--json`. The policy, store, and seam enforcement are unchanged)
**Extended-by:** [LLP 0190](./0190-wizard-defaults-gate.decision.md) (§never-silent below: the step's prompt shape changes - a defaults gate first, the menu's checkboxes now mark what syncs rather than what stays local, and locked sources appear read-only instead of not at all; the policy, store, and seam enforcement here are unchanged)

> Supersedes [LLP 0132](./0132-managed-local-additions-local-only.decision.md).
Expand Down
Loading
Loading