feat: persistent + runtime-toggleable enabled state (v1.3.0)#7
feat: persistent + runtime-toggleable enabled state (v1.3.0)#7royosherove wants to merge 4 commits into
Conversation
New settings key: `enabled: boolean` (default true). Two mechanisms: 1. **Persistence:** in-TUI toggle (Alt+R / /review) now writes the new state to ~/.pi/.hardno/settings.json atomically, so it survives session restart. Previously reset to true on each session_start. 2. **Runtime external toggle:** exposes isEnabledFromDisk() and writeEnabledToDisk(). The agent_end handler re-reads the `enabled` field from disk on each turn, so an external process (e.g. roundhouse's /toggle-review) can flip it by writing the file \u2014 the running session picks up the change without restart. Precedence (unchanged): local .hardno/settings.json wins over global ~/.pi/.hardno/. When local exists but has no `enabled` key, does NOT fall through to global \u2014 same-file-wins-on-silence semantics. Atomic write: tmp + rename. No torn writes on crash. Tests: 15 new (enabled parser + isEnabledFromDisk + writeEnabledToDisk). 435 total green. Typecheck + format clean. (Pre-existing lint error in dismiss.ts untouched.)
F1 (Medium): writeEnabledToDisk now writes to the effective settings file
(local if present, else global) via resolveWritePath(), matching the
read path. Previously: in-TUI toggle wrote global but a local file
without `enabled` masked it on next session_start \u2014 toggle appeared
to revert. New option signature: writeEnabledToDisk(enabled, { cwd, home, targetPath }).
Caller in index.ts now passes ctx.cwd.
F2 (Medium): race-retry in writeEnabledToDisk. Captures mtime at read,
checks it before rename; on mismatch, re-reads and retries (up to 3
attempts). Eliminates lost-update when roundhouse + pi-hard-no
write concurrently to non-`enabled` fields.
F2-semantic (isEnabledFromDisk): only fall through to next dir on ENOENT.
Malformed/permission/IO errors on the more-specific file now return
null without falling through \u2014 preserves "local wins on silence"
invariant even under corruption.
F3 (Low): back up original bytes to <settings>.corrupt-<ts>.bak before
overwriting a file whose content is unparseable or non-plain-object.
User data no longer silently nuked on malformed input.
F4 (Low): guard settings mutation consistently across both toggle paths.
F5 (Low): fix pre-existing lint error in dismiss.ts (`no-useless-escape`
on \- in char class). Boy Scout rule.
Also: orphan tmp file cleanup on write failure; dir mkdirp inside the
retry loop so each attempt is self-contained.
Tests: 91 settings.test.ts / 448 total green. New tests:
- resolveWritePath correctness
- writeEnabledToDisk with cwd routing (local present / absent)
- end-to-end toggle writes local + read picks it up (F1 regression)
- malformed/array local does not fall through to global (F2 regression)
- corrupt-bytes backup written before overwrite (F3 regression)
- loadSettings integration: enabled persists across load cycles (F4)
Architect review flagged README missed the new user-facing feature. - Added `enabled` row to settings.json example + settings table - Added a callout explaining the persistence + routing + runtime re-read semantics so users understand why external tools (roundhouse) can toggle without restarting the session
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 508d821a37
ℹ️ 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".
| if (diskEnabled !== null && diskEnabled !== orchestrator.isEnabled) { | ||
| log(`runtime toggle: disk says enabled=${diskEnabled}, updating orchestrator`); | ||
| orchestrator.setEnabled(diskEnabled); | ||
| // F4: guard like the toggleReview handler for consistency. `settings` |
There was a problem hiding this comment.
Preserve manual toggle when disk persistence fails
When writeEnabledToDisk fails (for example due to read-only config files or permission errors), the toggle handler still updates orchestrator.isEnabled, but the next agent_end unconditionally re-syncs from disk and overwrites that in-memory state. In this failure mode, Alt+R//review appears to work briefly and then flips back on the next turn, which regresses the prior in-memory-only behavior exactly when persistence is unavailable.
Useful? React with 👍 / 👎.
Response to 2026-05-12 Mini Shai-Hulud wave (169 npm packages, 373 malicious versions across @TanStack, @UiPath, @squawk, @mistralai, @TallyUI, @BeProduct, @draftlab, @draftauth, and others). Current exposure: NONE. Lockfile pins @mistralai/mistralai@2.2.1 with clean pre-attack integrity hash; compromised versions 2.2.2-2.2.4 were published 2026-05-11 22:45-22:53 UTC and are never resolved because npm ci respects the lockfile. This .npmrc closes the policy gap: repo was missing the org-wide min-release-age=7 quarantine that blocks any package published <7d ago from installing. Protects against future rogue publishes inside our existing semver ranges.
Problem
The Alt+R /
/reviewin-TUI toggle was in-memory only \u2014 every session_start reset it totrue. External tools (e.g. roundhouse gateway) couldn't ask pi-hard-no to turn review off without restarting the session.Solution
Add
enabled: booleantoAutoReviewSettings(defaulttrue, persisted in.hardno/settings.json). Two effects:In-TUI toggle now persists \u2014 Alt+R writes the new state to disk atomically, so it survives session restart.
External runtime toggle \u2014 pi-hard-no re-reads just the
enabledfield at eachagent_end. An external writer (like roundhouse's new/toggle-review) can flip the value by writing the settings file; the running session picks up the change on the next agent turn without restart.File routing
Write path matches read path (local > global). New
resolveWritePath(cwd):cwd/.hardno/settings.jsonexists \u2192 write there (local wins).~/.pi/.hardno/settings.json.Prevents the "toggle appears to revert" footgun where writing global would get masked by a local file with no
enabledkey on next session_start.Safety
.corrupt-<ts>.bakbefore overwrite (user data never silently nuked)isEnabledFromDisk\u2014 malformed/permission errors on the more-specific file halt resolution ("local wins on silence")Review history
3 review cycles + architect pass. Findings addressed:
Tests
isEnabledFromDiskprecedence-on-silence,resolveWritePath, routing end-to-end (toggle + re-read), malformed-data backup, load-cycle integrationno-useless-escapein dismiss.ts also fixed per Boy Scout rule)Pairs with
roundhouse PR
feat/toggle-code-review\u2014 adds/toggle-reviewTelegram slash command that uses this runtime mechanism.