Skip to content

feat(machine-health): add the short report and monthly schedule - #9243

Merged
kyle-sexton merged 15 commits into
mainfrom
feat/machine-health-short-report-schedule
Oct 22, 2026
Merged

kyle-sexton merged 15 commits into
mainfrom
feat/machine-health-short-report-schedule

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

No related issue: phase 11 of the owner-approved machine-health checkup extension plan (ADR 0081 series).

Summary

Each audit run now ends with a short report of at most 12 lines. /machine-health:setup schedule creates a monthly checkup, only on the owner's yes, that delivers it: a Claude Desktop scheduled task, or a Task Scheduler fallback running a wrapper copied into the state folder. The wrapper runs the audit unattended in dontAsk mode, allows only the two exact audit commands, and records whether the run really happened.

Fix

  • Format-ShortReport.ps1 prints the short report from state/latest.json: host and run id, elevation, severity counts and the change since the prior run, the worst three CRIT or WARN items, the intruder headline, the largest folder, and the report and page paths. The first line is always machine-health checkup <host> <run_id>, and every machine-derived string is collapsed to one line.
  • --scheduled audit mode: 60 s sampling, no elevated-pass offer, discovery only proposes checks in the report and TODO.md, and the short report is the whole final message. It types the roots into the two scripts' command lines so a permission rule can match them. When report_dir is unset it leaves out -OutputBase, and the orchestrator applies the audit's default report folder; -OutputBase is now optional.
  • Monthly wrapper (setup/templates/monthly-run.ps1): claude -p in dontAsk with a deny settings file (git, gh, downloads, subagents, writes outside TODO.md), no web tools and no MCP servers. Setup writes the plugin root, state folder and report folder into the copy, and its two Bash allow rules match the audit's --scheduled commands exactly, so a different -StateBase or any other argument is denied. A run counts as ok only on exit 0, no error, the marker line naming latest.json's run id, and a newer latest.json. Results go to a status file written whole, then renamed.
  • Setup: schedule refuses a root holding a quote (', U+2018-U+201B), a control character or % & | < > ^. It shows the owner's own shell, write and network allow rules and the report folder before asking. Setup check reports the newest status file and fails a stale or never-fired copy.

Verification

  • Pester suite on the rebased head: 1412 passed, 0 failed, 1 skipped (Invoke-MachineHealthTests.ps1, pre-existing non-Windows skip). checkup-view.test.sh, check-shell-portability.sh origin/main, markdownlint and typos are clean.
  • Independent phase verifier: every Sanity Check bullet passed except the owner-gated runtime one, which was not run. Host smoke: an orchestrator dry run, then Format-ShortReport printed 8 lines with the fixed first line. An omitted -OutputBase wrote the report under the roots-block default. A holdout typing -ReportRoot '${user_config.report_dir}' into the schedule command failed 9 of 9 hostile-root cases.
  • Independent Fable review: one important finding. The allow rules ended in *, so a steered session could pass another -StateBase, whose catalog overlay names its own check scripts. Fixed with exact rules. A claude -p 2.1.305 dontAsk probe confirmed that an exact Bash(pwsh -NoProfile -File '<script>' -StateBase '<x>') rule runs that command and denies the same command with another -StateBase. Its suggestions were also taken: a report root is checked at setup, and % & | < > ^ are refused at setup.
  • Left out: the runtime acceptance (one real monthly-run.ps1 run with ok: true, and a Desktop Run now), which waits on the owner creating the task after merge. Also left out: atomic writes for the two installed files (Fable suggestion 4). A half-written copy fails check step 6 through Test-MonthlyRunCurrent, and rerunning schedule rewrites it.

Related

🤖 Generated with Claude Code

kyle-sexton and others added 13 commits October 22, 2026 19:12
Format-ShortReport.ps1 prints the closing run summary, at most 12 lines,
from latest.json, with every line collapsed by Format-MachineText so no
machine string can add a line or forge the fixed first line. The audit
skill runs the orchestrator in the background, maps --scheduled to a
60 s sample and no elevated offer, and relays the short report last.

Setup gains a schedule step: a Desktop task or a Task Scheduler task
running the monthly-run.ps1 template copied to <StateBase>/scheduler/,
created only on the owner's yes. The wrapper counts a run ok only on
exit 0, is_error false, the short report's first line and a newer
latest.json, stops the process tree at its time limit, and writes a
status file that setup check reports. Setup check also accepts the
process mute kind top-processes emits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Records the process kind, the run kind in startup identity, the
detection id in detection identity, and unkeyed per-version firewall
rules, as the machine-health checks shipped them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The setup schedule step refuses a state or plugin root holding a single
quote or control character, shows the created task's action, and no
longer restates the cron and schtasks schedule values in prose. The
audit skill states the --scheduled parameters once. The short-report
test fixtures use named parameters and non-state-changing verbs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The orchestrator now records run_stamp, report_path, page_path and
cadence_deferred_count in latest.json, and stamps the run with the
invariant culture so a non-Gregorian culture cannot name files by the
wrong year. Format-ShortReport prints the recorded paths instead of
rebuilding them, passes the deferred count to the delta, reads two history
lines, and names the host the way the orchestrator does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…am wait

The wrapper now runs the audit with --permission-mode dontAsk and an exact
tool list (Read, Edit of TODO.md, pwsh -NoProfile -File through Bash or
PowerShell), denies WebFetch and WebSearch, and loads no MCP server. It
counts the marker only when the result's first line names latest.json's
run_id, reads output in chunks so a process left holding stdout cannot
hang it, and records streams_incomplete. The shim's result is the real
short report, tying the marker to Format-ShortReport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s run age

Under --scheduled, discovery records proposals in the report and TODO.md
and authors or registers no check, and the short report is the run's whole
final message. Setup's schedule step describes the dontAsk tool list and
its limits, refuses the fallback for a synced or --plugin-dir state folder,
and check step 6 fails a status file older than 35 days and notes a stale
wrapper copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… checks

The latest.json artifact test now runs weekly with two checks re-enabled at
monthly cadence that ran three days ago, so cadence_deferred_count must be 2,
and reads the snapshot through JsonNode instead of ConvertFrom-Json -DateKind,
which PowerShell 7.4 lacks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fallback wrapper's allow rules were `pwsh -NoProfile -File *`, which
also matched `-File -` (a script piped on stdin) and any .ps1 on disk.
Setup now writes the plugin root into its copy, so the rules name exactly
Invoke-MachineHealthCheck.ps1 and Format-ShortReport.ps1 under that root,
and the audit writes those two commands from ${CLAUDE_PLUGIN_ROOT}, the
same substitution. `-File -` is denied outright.

The run also gets --settings scheduled-settings.json, whose deny rules
beat the owner's own allow rules for git, gh, download commands,
subagents and other writing tools. The wrapper refuses to start without
it, or once the pinned root is gone, and refuses a .cmd claude when an
argument holds a character cmd.exe would rewrite. Its stderr log names
the claude it ran, and the status file is written then renamed.

Setup check fails a copy left stale by a plugin update, a copy that
produced no status file in 35 days, and an unreadable status file.
Schedule shows the owner their own shell, write and network allow rules
before either task is created, and the Desktop route says it passes no
tool list. TODO.md docs name Write or Edit, both covered by the one
Edit(/TODO.md) rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d refuse quote-bearing roots

The roots block keeps typed paths out of every PowerShell command, but a
permission rule matches command text, so the monthly wrapper can pin its
two scripts only by a path typed in the command. The two --scheduled
commands stay typed, in text blocks, as the one exception the roots block
names.

- A claude -p probe in dontAsk showed the PowerShell tool approves no pwsh
  call by rule (PowerShell(pwsh *) and an exact rule both denied), while
  Bash(pwsh -NoProfile -File '<script>' *) runs a plain call and denies one
  carrying $( ) in its arguments. The wrapper drops its PowerShell allow
  twins, and the audit runs the scheduled commands through the Bash tool.
- Assert-ScheduleRoot refuses a plugin root or state folder holding ', U+2018
  to U+201B, a control character or a line break. Get-MonthlyRunText and
  Install-MonthlyRun call it, and setup's schedule step runs it before
  either route, so the wrapper's own root literal and the audit's typed
  roots stay data.
- Setup's check, schedule refusal, install and run-now commands now append
  to the roots block.
- Tests: the wrapper's Bash allow rules must match the two scheduled
  commands audit SKILL.md prints; every quote character and line break is
  refused; the setup templates parse with hostile roots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A scheduled run could not type the default report folder: dontAsk leaves
it no command that reads USERPROFILE, and an environment variable inside
single quotes stays literal. The orchestrator now applies the audit's
default when -OutputBase is omitted, and the scheduled command leaves it
out when report_dir is unset. Setup's roots block reads report_dir, and
schedule refuses a report folder the run cannot quote and shows the
folder the monthly runs write to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lashes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wrapper's allow rules ended in a wildcard, so the scheduled session
could pass any -StateBase, and the orchestrator runs a catalog overlay's
check scripts from whatever state folder it is given. Setup now writes
the plugin root, state folder and report folder into the wrapper, whose
two rules match the audit's --scheduled commands exactly; a changed
report_dir leaves the copy stale. Setup also refuses % & | < > ^ in a
root, which the wrapper will not pass through a .cmd claude.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 22, 2026 23:16
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T23:18:57.009550Z 27dbddd Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed cac7b262bd2c6fd770d2a68a41cd1b0960a1390d: 0 finding(s), 0 as inline comments.

Incremental security review of plugins/machine-health/skills/audit/SKILL.md (step 5) since c24391a: no security findings.

The change adds one clause: on a run-lock result the run goes straight to step 8's run-lock sentence, skips the report check and the decision-page sentence, and offers no elevated pass. This narrows what the run does. It removes no standing guardrail, since the run still writes nothing and does no discovery. The decision-page rule that forbids acting on pasted payloads is in the skipped sentence, but that sentence only applies when a page was built, and a run-lock run builds none. No plugin manifest was touched, so there are no manifest <field> lines. I did not read the instruction exception register, so the protected-class list was not consulted. I judged the change by consequence: the run does less, and no check was removed.

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed cac7b262bd2c6fd770d2a68a41cd1b0960a1390d: 0 finding(s), 0 as inline comments.

No findings. The only change since c24391a is one sentence in step 5 of plugins/machine-health/skills/audit/SKILL.md.

On a run-lock result, step 5 now says to go straight to step 8's run-lock sentence, skip step 8's report check and decision-page sentence, and offer no elevated pass.

I read steps 5 and 8 and the Elevated pass section and found no conflict:

  • The text now matches step 8 (lines 155–181). The report check ("Confirm the report exists") and the decision-page sentence are the first part of step 8. Step 8 already prints only the fixed first line plus the run-lock summary on a run-lock result, so skipping them fits a run that wrote no report and no latest.json.
  • The Elevated pass section says the pass comes "after step 8" and is offered to a present owner on an unelevated run. It has no run-lock exception of its own, so step 5 is the only place that rules it out. That is enough, because step 5 is the first instruction a run-lock run reads.

I did not run the Pester suite or any scripts. This is a doc-only change and I reviewed the text only. The PR's verification claims (1412 passed, 0 failed) are the author's and I did not check them. No closing issue exists ("No related issue"). No manifest was touched, so there are no manifest lines.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 27dbddd3e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…n-lock

The audit and setup schedule text restated how permission rules,
dontAsk, deny precedence, Desktop missed runs and Git Bash path
conversion behave. Each now states our decision and points at the
anchored docs section. A run-lock result now skips discovery and the
short report, whose latest.json belongs to an earlier run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…levated pass

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit 6b671f9 Oct 22, 2026
28 checks passed
@kyle-sexton
kyle-sexton deleted the feat/machine-health-short-report-schedule branch October 22, 2026 23:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant