Skip to content

Release 1.0.0 — close the ways enforcement failed silently, and make setup two questions - #683

Merged
NiveditJain merged 9 commits into
mainfrom
fix/silent-failure-sweep
Aug 12, 2026
Merged

Release 1.0.0 — close the ways enforcement failed silently, and make setup two questions#683
NiveditJain merged 9 commits into
mainfrom
fix/silent-failure-sweep

Conversation

@chhhee10

@chhhee10 chhhee10 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

First stable release. latest has sat on 0.0.15 for the whole beta line, because publish.yml resolves the dist-tag as next from a branch, beta for a prerelease on main, else latest — so every 1.0.0-beta.N went to beta. Dropping the suffix is what moves latest, which means npm install -g failproofai and the bare npx -y failproofai in every installed hook command start resolving here instead of to 0.0.15.

The theme

Every fix below is one bug wearing different clothes: the user's belief about their protection diverged from reality and nothing reconciled the two. None of them were crashes or test failures — all are success paths, which is why none were caught.

Enforcement that wasn't

  • PostToolUse denies were dropped on codex and copilot. Both read a top-level {decision:"block", reason}; we sent hookSpecificOutput.additionalContext, which neither reads. So the policy ran, the verdict was logged, the dashboard counted it as enforcement — and nothing happened. Verified rather than inferred: a live A/B probe on codex 0.147.0 (block shape → hook: PostToolUse Blocked, the reason replaces the tool result and the model never sees the real output; our shape → Completed, model reads it verbatim), and a re-read of the shipped copilot 1.0.78 bundle where both postToolUse sites gate on vK = t => t?.decision==="block" && typeof t.reason==="string".
  • mode: "oss" never stopped the daemon. --disconnect writes the flag and claims "every cloud code path keys off this flag" — true of the CLI, false of the daemon, which holds the socket and had never read it. It now reads mode.kind (the shape fp-config.ts actually persists; reading it as a string is why an earlier cut of this fired zero times).
  • A revoked key reported as connected. --status read the credential file, which records --connect time and is never revisited. The uploader already writes its verdict into the parked batch's filename and already declines to retry a definitive refusal — nothing read that directory. Now --status reports from it, and the same verdict fires once per session at SessionStart, so it surfaces with no flag and no user action.
  • A Hermes session arrived under two agent ids. Confirmed on a customer org from a single collector. The id was derived from the session's own cwd/source and re-read every poll, and Hermes rewrites those throughout a run — so a session split the moment one changed. Identity now comes from which database the session is in, which cannot change mid-poll. Matches the standalone collector's scheme, so migrating machines are not renamed.

Setup is two questions

failproofai config opens with Recommended vs Customize instead of asking scope, bundles, harnesses and cloud of somebody who just installed the tool. Recommended is a decision taken on their behalf — global scope, detected CLIs, a named 15-policy set — not a shortcut past the decisions. Customize is the previous wizard, unchanged.

RECOMMENDED_POLICIES is written out with its reasoning rather than derived from defaultEnabled, and includes three that were off and should not have been: block-rm-rf, block-force-push, block-secrets-write. It unions with what was already enabled, because installHooks runs with replace: true and writing the bare 15 would turn "give me the defaults" into a reduction in protection.

Setup's prose is down by two thirds. The consent clause stays — this is the only place transcripts are ever disclosed.

Tests that could not fail

Two are worth calling out because they were worse than no test:

  • The mode veto's fixtures carried the same wrong assumption as the code, so they reported a broken case as covered. Fixing them surfaced five more tests that read the developer's own ~/.failproofai/config.json — passing on a machine that had not been set up, failing on one that had.
  • Every Hermes test polled once. The bug only appears across two polls, which is why it shipped behind a green suite. The regression test polls twice with cwd rewritten in between.

Gates

3456 TS tests · 28 Rust suites · tsc, clippy and fmt clean.

Known, and deliberately not in this PR

  • enforcement-capability.ts's codex rows cite source paths that no longer exist in 0.147.0. Marked unverified rather than left looking like evidence — a re-probe is outstanding.
  • The bundle vocabulary has a hole: four defaultEnabled policies are in no preset, so a first run that picks bundles omits the two that stop the agent disabling failproofai. Recommended routes around it; the bundles still have it.
  • The hook source still derives its agent id from cwd, so it can disagree with the Hermes session source. Same instability class, not addressed here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HEwcerc9jE7ZBkfBiYRbep

Hermes review

Field Value
Status Changes requested
Reviewed commit 3242b91f4d9cf5e9fae42c6d19736ed03f3488a1
Policy revision 1d8f31d926828f3bae215c58f5b35baa44acbff0
Model gpt-5.6-terra
Duration 289s
Updated 2026-08-12T11:33:05.412958812+00:00

Summary

The PR fixes the prior default-Claude fallback, but Recommended setup can now finish with no hooks at all when no supported CLI is detected. Static parsing of modified TypeScript passed in an isolated container; dependency-backed tests could not run because the isolated container has no cached packages and network access is disabled.

Changes

  • Releases version 1.0.0 and updates Cloud branding.
  • Adds recommended global setup and policy presets.
  • Fixes Codex/Copilot PostToolUse deny payloads.
  • Adds rejected-delivery health reporting.
  • Makes daemon OSS mode and Hermes profile identities behave consistently.

Validation

  • Passed docker run --rm --network=none -v /review/input/workspace:/workspace:ro -w /workspace oven/bun:latest sh -lc 'bun build src/hooks/configure-wizard.ts --target=node --external="*" --outfile=/tmp/configure-wizard.js && bun build src/hooks/delivery-health.ts --target=node --external="*" --outfile=/tmp/delivery-health.js && bun build src/hooks/policy-evaluator.ts --target=node --external="*" --outfile=/tmp/policy-evaluator.js' — All three modified TypeScript modules parsed and bundled successfully in an isolated container. (0s)
  • Skipped docker run --rm --network=none -v /review/input/workspace:/workspace:ro -w /tmp oven/bun:latest sh -lc 'cp -a /workspace review && cd review && bun install --frozen-lockfile --offline && bunx vitest run __tests__/hooks/configure-wizard.test.ts __tests__/hooks/delivery-health.test.ts' — The isolated container has no dependency cache; installing requires registry access, which is intentionally disabled by the harness network isolation. (0s)

Findings

  • High/High Recommended setup silently applies no protection when no CLI is detected — recommendedClis comes only from detectInstalledClis() (src/hooks/configure-wizard.ts:1122-1124). Recommended mode assigns that list directly to clisSel (line 1260), while the apply loop calls installHooks only when clisForScope.length > 0 (line 1791). With no detected supported CLI, the wizard still completes and marks the daemon configured, but installs no hooks and never persists the recommended policy set. (src/hooks/configure-wizard.ts:1260)

Open questions

None.

Policy overrides

None.

Summary by CodeRabbit

  • New Features
    • Released FailproofAI 1.0.0.
    • Added Recommended and Customize setup modes with policy previews and preservation of existing policies.
    • Added delivery-health reporting, including rejected batches and credential-related issues.
    • Added session-start warnings for rejected cloud deliveries.
    • Improved Hermes profile identity stability.
    • Added fail-closed enforcement verification, unsupported-platform safeguards, and OSS cloud-disconnect enforcement.
  • Bug Fixes
    • Corrected tool-blocking responses for Codex and Copilot integrations.
  • Documentation
    • Updated cloud branding and Codex capability guidance.

chhhee10 and others added 7 commits August 12, 2026 11:39
Each of these was a success path: the policy ran, the verdict was recorded,
the dashboard counted it, and nothing happened. None of them could fail a
test or raise an error, which is why none were caught.

PostToolUse denies now enforce on codex and copilot. Both read a top-level
{decision:"block", reason} and neither reads the hookSpecificOutput shape we
sent, so every PostToolUse deny on them was dropped. Verified rather than
inferred: a live A/B probe on codex 0.147.0 (block shape -> "hook: PostToolUse
Blocked", the reason REPLACES the tool result and the model never sees the real
output; our shape -> "Completed", model reads it verbatim) and a re-read of the
shipped copilot 1.0.78 bundle, where both postToolUse call sites gate on
vK = t => t?.decision==="block" && typeof t.reason==="string". Re-probing codex
also surfaced that several of its capability rows cite source paths that no
longer exist in 0.147.0; they are marked unverified rather than left looking
like evidence.

--status stops reporting a connection the machine no longer has. Everything it
printed came from the credential file, which records --connect time and is
never revisited, so a revoked or expired key left it correct while nothing
arrived. The uploader already writes its verdict into the parked batch's
filename and already declines to retry a definitive refusal; nothing read that
directory. It does now, and the same verdict is emitted once per session at
SessionStart so it surfaces with no flag and no user action.

The daemon honours mode: "oss". --disconnect claimed every cloud code path keys
off that flag; the daemon had no reference to it and decided from credential
files alone, so a surviving credentials.json or legacy cloud.json kept it
polling while the CLI said disconnected. Only an explicit "oss" vetoes, since
mode postdates enrolments in the field.

The daemon reports the deployment it is actually enforcing, from the active.json
it already materialises, on the poll it already makes. The server has only ever
been able to infer delivery from timestamps.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Setup opened by asking scope, policy bundles, harnesses and cloud of somebody
who had just installed the tool and did not yet know what any of them meant.
Every one of those has a defensible default, so asking all four up front made
the person least able to answer do the most work.

Recommended is not a shortcut past the decisions, it is a decision taken once
on their behalf: global scope, the CLIs actually detected on this machine, and
a named 15-policy set. Customize is the previous wizard unchanged. The cloud
question is still asked on both paths.

RECOMMENDED_POLICIES is written out rather than derived from defaultEnabled,
because those answer different questions — defaultEnabled seeds a checklist of
40, this answers "what should guard a machine whose owner did not want to
choose" — and deriving one from the other would reshape the recommended set
every time somebody flipped a flag on an unrelated policy. It is the 12 already
default-on plus three that were off and should not have been: block-rm-rf,
block-force-push and block-secrets-write. Both of the first two are precisely
scoped — block-rm-rf only fires at depth <=2 under / or a home directory,
exempts /tmp and treats an unresolved $VAR as catastrophic, so rm -rf
node_modules is untouched; block-force-push allows --force-with-lease and
--force-if-includes. The require-*-before-stop gates, the infra blockers,
block-read-outside-cwd and the ten warn-* policies are excluded, each for a
reason recorded beside the list.

Two things the path must not do, both of which the customize expressions would
have done. It writes the UNION of the recommended set and whatever was already
enabled, because installHooks runs with replace: true and the bare list would
switch off anything added by hand — turning "give me the defaults" into a
reduction in protection. And it leaves customPoliciesEnabled alone, because the
customize expression reads false when no bundle is ticked and no bundle is ever
ticked here, which would disable every .failproofai/policies file on disk.

Recommended names all 15 rather than composing bundles because it cannot be
composed from them: Dangerous Commands is in no preset, so 8 of the 12
default-on policies are bundle-reachable and 4 are not — including the two that
stop the agent disabling failproofai itself. defaultsMissingFromRecommended()
and a test keep the two lists from drifting apart. The bundle gap itself is
still open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three loose ends from the recommended path, all of them about the user being
able to see and keep what is theirs.

The review screen said "Policies : 15 enabled" — a number they cannot check
and, on this path, did not choose, which makes that screen the one place they
find out what they agreed to. It now names them, sorted and wrapped to the
same 77-column budget the other summary lines use: writeLines truncates with a
hard cut and no ellipsis, so an over-long line ends mid-slug and reads as a
policy name that does not exist. Capped at 20, because "Everything" is 40 and
a wall of slugs is not transparency; past the cap the count stands with the
command that prints the full list, and nothing is half-rendered — a truncated
list of security policies invites the reader to assume the tail is more of the
same.

Custom and convention policies survive, and that is now asserted rather than
reasoned about. The wizard passes no customPoliciesPath and
removeCustomHooks: false, so neither branch in installHooks that rewrites
customPoliciesPaths runs, and {...previousConfig} carries both it and
customPoliciesEnabled through even under replace: true. A manager test pins
it: the enabled set is replaced as asked while an explicit -c path and the
convention flag are untouched.

The union is tested too, seeded as a real config in the suite's isolated HOME
rather than a mock, because readScopedHooksConfig is the genuine
implementation there. Recommended adds to what was already enabled and never
removes it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Setup opened by explaining itself. The daemon step spent three lines on the
warm-worker architecture to somebody who was about to type a password, and the
cloud step spent five on what connecting sends. Eleven lines of prose became
three: say what is happening and what it costs, and drop the mechanism.

The cloud screen keeps the one clause that is not explanation — "Sessions
include prompts, file contents and command output" — because this is a consent
screen and it is the only place that disclosure is ever made (describeOutcome
prints "hook activity" afterwards and never mentions transcripts). Compressing
it to "telemetry" would be brevity that is really vagueness.

Its option hint now says what the cloud GIVES rather than what it takes: "see
what your agents did" was wrong, because the local dashboard already shows that
and it made connecting look redundant. Central monitoring and policy deployment
are what the key's two scopes actually buy. The stay-local hint points at
`config` rather than `--connect`, which is the non-interactive flag and not
something to send a person who just declined.

"No key?" now points at befailproof.ai/get-started rather than the dashboard
host: a person reading that line has no key, which usually means no org either,
and a marketing route is the safer thing to hard-code into a CLI than an in-app
path that can be reorganised.

Failproof Cloud -> FailproofAI Cloud throughout, including two occurrences in
bin/failproofai.mjs's help text that a sweep over src/ missed — caught by
grepping the built bundle rather than trusting the source edit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The daemon's `mode: "oss"` veto never fired. It read `mode` as a string, but
`fp-config.ts` persists `mode: { kind }` — an object — so `as_str()` returned
None and a machine put back on OSS kept polling and shipping exactly as it had
before the veto existed.

The unit tests passed because their fixtures were written from the same wrong
assumption as the code. A test that encodes the bug it is meant to catch is
worth less than no test, because it also reports the case as covered. The
fixtures now use the real shape, and the old flat-string form is kept as a case
that must NOT veto — it is precisely what the original fixtures said.

Correcting it surfaced a second problem: five existing tests began failing,
because `with_credentials_file` never set FAILPROOFAI_HOME and so read the
DEVELOPER's own ~/.failproofai/config.json. They passed on a machine that had
not been set up and failed on one that had. That helper now isolates the home
too — a unit test whose result depends on the laptop running it is not testing
what it says.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A single session was arriving under two agent ids. Confirmed on a customer org:
`20260812_133702_31ca19f0` under both `hermes-kratos` and `hermes-telegram`,
and a cron session under both `hermes-cron` and the bare fallback — from ONE
collector, with the other producer's rows excluded.

The id was derived from the session's own `cwd` and `source` columns, re-read on
every poll. Hermes rewrites those throughout a run (`hermes_state.py` carries
~20 `UPDATE sessions SET …`), so a session split the moment one changed between
two polls. The file documented the assumption as a safety property — "session
columns are read fresh on every poll" — while the `pending` map directly below
states the opposite rule correctly for tool names. Reading fresh is right for a
name and wrong for an identity.

Identity now comes from WHICH DATABASE the session is in: the root keeps the
bare `hermes` every deployment already ships under, and profiles/<name>/state.db
becomes `hermes-<name>`. A file path cannot change under us mid-poll, so this is
stable by construction rather than by remembering anything, and it keeps the
poll function pure — the format contract requires that, or re-read rows dedup
into duplicates instead of collapsing. It also matches the standalone
collector's `agent_id_for`, so a machine migrating off it is not renamed.

Nothing is lost: `hermes_source` and `hermes_cwd` were already emitted on every
event, so "which transport" and "which project" stay answerable as filters over
one agent's sessions — which is what they should have been rather than a reason
to fragment one database into several agents.

The regression test polls twice with `cwd` rewritten in between. Every existing
test polled once, which is why a shipping bug sat behind a green suite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First stable release. The version drops its prerelease suffix in package.json
and Cargo.toml together — CI checks them against each other because the release
tag the CLI builds its daemon download URL from is the npm version, while the
binary at that URL reports the Cargo one.

This also changes where the package lands. `publish.yml` resolves the dist-tag
as "next from a branch, beta for a prerelease on main, else latest", so every
1.0.0-beta.N published from main went to `beta` and `latest` has sat on 0.0.15
for the whole beta line. A version without a suffix is the thing that moves it —
which means `npm install -g failproofai` and the bare `npx -y failproofai` in
every installed hook command start resolving to this release rather than to
0.0.15.

The CHANGELOG's top section becomes `1.0.0` and carries what was accumulating
under `1.0.0-beta.22`, with two paragraphs stating the guarantees somebody
arriving from a beta needs to know: a configured machine fails closed through
the daemon, and a policy that cannot be honoured on a given CLI is recorded as
unverified rather than silently reported as blocking. The beta sections stay
below as their own history.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 74b6a917-0cb7-4433-b4b0-5e7082b1be2e

📥 Commits

Reviewing files that changed from the base of the PR and between a7b5eae and 3242b91.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • __tests__/e2e/helpers/hook-runner.ts
  • __tests__/e2e/hooks/codex-integration.e2e.test.ts
  • __tests__/e2e/hooks/copilot-integration.e2e.test.ts
  • src/hooks/configure-wizard.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.md
  • src/hooks/configure-wizard.ts

📝 Walkthrough

Walkthrough

The project moves to version 1.0.0. It adds Recommended setup, policy previews, delivery-health reporting, OSS cloud-disconnect handling, applied-deployment polling, updated Codex and Copilot deny responses, and stable Hermes identities.

Changes

Stable release metadata

Layer / File(s) Summary
Release metadata and product references
CHANGELOG.md, Cargo.toml, package.json, README.md, src/..., bin/..., __tests__/...
The project version becomes 1.0.0. Release notes, attribution, and product references are updated.

Setup and policy selection

Layer / File(s) Summary
Recommended setup and policy selection
src/hooks/configure-wizard.ts, src/hooks/policy-presets.ts, __tests__/hooks/configure-wizard.test.ts, __tests__/hooks/policy-presets.test.ts, __tests__/hooks/manager.test.ts
The wizard adds Recommended and Customize paths, bounded policy previews, global Recommended setup, and union-preserving policy writes. Tests validate the 15-policy Recommended set and setup behavior.

Delivery reporting

Layer / File(s) Summary
Delivery health reporting
src/hooks/delivery-health.ts, src/hooks/cloud-enrollment-cli.ts, src/hooks/handler.ts, __tests__/hooks/delivery-health.test.ts
Failed batches are parsed and summarized. Cloud status and SessionStart report definitive delivery rejections without changing hook outcomes.

Cloud enrollment

Layer / File(s) Summary
Cloud enrollment and desired-state polling
crates/failproofaid/src/cloud_client.rs
File enrollment is suppressed only for explicit OSS mode. Desired-state requests can include the applied deployment identifier, with schema and state handling covered by tests.

CLI enforcement

Layer / File(s) Summary
CLI enforcement response contracts
src/hooks/policy-evaluator.ts, src/hooks/enforcement-capability.ts, __tests__/hooks/policy-evaluator.test.ts, __tests__/e2e/...
Codex and Copilot receive top-level PostToolUse block responses. Claude retains nested hook output. Capability documentation and end-to-end tests record the updated behavior.

Hermes identity

Layer / File(s) Summary
Hermes profile-scoped identity
crates/fpai-collect/src/sources/hermes/*, crates/failproofaid/src/main.rs, crates/fpai-collect/tests/hermes_source.rs
Hermes IDs derive from database paths. Session metadata remains event data and no longer changes agent identity.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

Suggested labels: bug, enhancement

Suggested reviewers: niveditjain, hermes-exosphere

Poem

I’m a rabbit with policies neatly in line,
Recommended paths make the setup shine.
Hermes hops steady from profile to track,
Cloud health reports each batch sent back.
Codex blocks cleanly; releases stand tall—
One point oh, and carrots for all! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the 1.0.0 release, silent-enforcement fixes, and simplified setup flow.
Description check ✅ Passed The description thoroughly explains the release, fixes, setup changes, tests, validation results, and known exclusions, despite not using all template sections.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hermes-exosphere

Copy link
Copy Markdown
Contributor

Hermes

Status Reviewing
Verdict Not reviewed yet
Head a7b5eaef16b6
Rounds 0 of 5

No summary yet.

What this changes

No component map for this revision.

Rounds

No review has finished on this pull request yet.

Findings

Nothing raised yet.


@hermes-exosphere help lists every command. This comment is maintained in place — I rewrite it after each review rather than posting a new one.

@coderabbitai coderabbitai Bot added bug Something isn't working enhancement New feature or request labels Aug 12, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

🧹 Nitpick comments (4)
crates/fpai-collect/src/sources/hermes/mod.rs (2)

297-331: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Unify the profile-name normalization used for agent ids and cursor keys. Two functions now derive a profile name from the same database path under different rules: hermes::profile_of returns the raw directory name for the agent id, and profile_dir_name returns a sanitized, 32-character-capped name for the cursor directory and health key. A profile directory named Prod A therefore yields agent id hermes-Prod A and cursor directory Prod-A.

Identity stability is not affected, because both derivations read the path rather than a mutable column. Cursor and health keying stay collision-free, because hermes_reserved uses profile_dir_name consistently. The remaining costs are an agent_id that can carry spaces, non-ASCII bytes, and unbounded length, and an operator who cannot map an agent id back to its cursor directory by name.

  • crates/fpai-collect/src/sources/hermes/mod.rs#L297-L331: normalize the profile segment inside agent_id_for, or document that callers must pass an already-normalized name.
  • crates/failproofaid/src/main.rs#L1085-L1093: derive one profile name per database and use it for the agent id, the cursor directory, and the health key, instead of calling profile_of and profile_dir_name separately.

Confirm the ingest side places no constraint on agent_id before choosing to leave the raw name in place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/fpai-collect/src/sources/hermes/mod.rs` around lines 297 - 331, Unify
profile-name normalization across Hermes agent IDs, cursor directories, and
health keys. In crates/fpai-collect/src/sources/hermes/mod.rs:297-331, normalize
the profile segment within agent_id_for or explicitly require callers to provide
an already-normalized name; in crates/failproofaid/src/main.rs:1085-1093, derive
one profile name per database and reuse it for the agent ID, cursor directory,
and health key instead of separately calling profile_of and profile_dir_name.
Confirm ingest imposes no agent_id constraint before retaining raw names.

297-331: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Normalize profile names before building agent IDs.

profile_dir_name bounds and sanitizes the same profile name, but agent_id_for emits it unchanged. Use a shared normalizer if agent IDs require the same safe, bounded format. Preserve existing IDs during migration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/fpai-collect/src/sources/hermes/mod.rs` around lines 297 - 331, Update
agent_id_for to normalize named profiles with the same bounded, sanitized logic
used by profile_dir_name before constructing the “{base}-{name}” ID. Reuse the
shared normalizer rather than duplicating it, and preserve the existing
root-profile ID and established IDs during migration where normalization would
otherwise rename them.
crates/fpai-collect/src/sources/hermes/transform.rs (1)

147-183: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the ignored _meta parameter from agent_id.

The function has one caller at crates/fpai-collect/src/sources/hermes/mod.rs:178. Remove the parameter and update that call. SessionMeta.source and SessionMeta.cwd remain used by with_session_context.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/fpai-collect/src/sources/hermes/transform.rs` around lines 147 - 183,
Remove the unused _meta parameter from the hermes agent_id function and update
its sole caller in the Hermes source module to pass only profile_agent_id. Keep
SessionMeta usage unchanged in with_session_context.
crates/fpai-collect/tests/hermes_source.rs (1)

1143-1143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add direct coverage for profile identity helpers.

The existing fixture tests profile path discovery, but no test exercises the profile branches of agent_id_for or profile_of. Add assertions for hermes-<name>, the profile name, and None for the root database.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/fpai-collect/tests/hermes_source.rs` at line 1143, Add direct
assertions in the existing Hermes fixture test for the profile identity helpers:
verify agent_id_for returns “hermes-<name>”, profile_of returns the profile
name, and profile_of returns None for the root database. Reuse the fixture’s
existing profile and root database handles.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 23-43: Update every new changelog entry in the Features, Fixes,
and Docs sections that currently ends with “(`#PR`)” to use the actual PR
reference “(`#683`)”, preserving the existing descriptions and formatting.

In `@crates/failproofaid/src/cloud_client.rs`:
- Around line 575-585: Update EnvGuard::drop to remove the FAILPROOFAI_HOME
environment variable in its existing cleanup block, alongside the
scratch-directory cleanup, so later tests do not retain the deleted path.

In `@src/hooks/configure-wizard.ts`:
- Around line 1253-1261: Prevent the Recommended-mode branch in
configure-wizard.ts (lines 1253-1261) from assigning an empty recommendedClis
selection; when no supported CLI is detected, route the user to CLI selection or
Customize so setup cannot complete without hook installation. Add coverage in
__tests__/hooks/configure-wizard.test.ts (lines 605-655) asserting the
no-detected-CLI path does not complete setup or skip hook installation.

In `@src/hooks/delivery-health.ts`:
- Around line 144-146: Update the filename filter in the entries loop of
delivery-health processing to accept only names ending in .jsonl or
.jsonl.poison, excluding names such as batch.jsonl.tmp before they affect total
or oldest-batch selection. Add a focused test covering that .jsonl.tmp entries
are ignored.
- Around line 75-88: Update the suffix parsing in the delivery-health logic
around cIdx and aIdx to accept only non-empty ASCII decimal digits, rejecting
whitespace, exponent, hexadecimal, decimal-point, signed, and other non-decimal
forms. Enforce the uploader’s bounds before stripping suffixes: clientStatus
must fit u16 and attempt must fit u32; otherwise leave the base and existing
values unchanged. Add regression tests covering non-decimal and out-of-range
suffixes.

In `@src/hooks/handler.ts`:
- Around line 615-638: Add a handler-level regression test covering
handleHookEvent with both canonical and mapped raw SessionStart events,
asserting deliveryHealth rejection warnings while stdout, stderr, and exitCode
remain unchanged. Add coverage for a non-SessionStart event that verifies
delivery health is neither read nor reported, without modifying existing tests.

---

Nitpick comments:
In `@crates/fpai-collect/src/sources/hermes/mod.rs`:
- Around line 297-331: Unify profile-name normalization across Hermes agent IDs,
cursor directories, and health keys. In
crates/fpai-collect/src/sources/hermes/mod.rs:297-331, normalize the profile
segment within agent_id_for or explicitly require callers to provide an
already-normalized name; in crates/failproofaid/src/main.rs:1085-1093, derive
one profile name per database and reuse it for the agent ID, cursor directory,
and health key instead of separately calling profile_of and profile_dir_name.
Confirm ingest imposes no agent_id constraint before retaining raw names.
- Around line 297-331: Update agent_id_for to normalize named profiles with the
same bounded, sanitized logic used by profile_dir_name before constructing the
“{base}-{name}” ID. Reuse the shared normalizer rather than duplicating it, and
preserve the existing root-profile ID and established IDs during migration where
normalization would otherwise rename them.

In `@crates/fpai-collect/src/sources/hermes/transform.rs`:
- Around line 147-183: Remove the unused _meta parameter from the hermes
agent_id function and update its sole caller in the Hermes source module to pass
only profile_agent_id. Keep SessionMeta usage unchanged in with_session_context.

In `@crates/fpai-collect/tests/hermes_source.rs`:
- Line 1143: Add direct assertions in the existing Hermes fixture test for the
profile identity helpers: verify agent_id_for returns “hermes-<name>”,
profile_of returns the profile name, and profile_of returns None for the root
database. Reuse the fixture’s existing profile and root database handles.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f6bdf336-e135-4dce-b2ec-7784a90fd532

📥 Commits

Reviewing files that changed from the base of the PR and between 481ac17 and a7b5eae.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • CHANGELOG.md
  • Cargo.toml
  • README.md
  • __tests__/audit/scheduled-audit.test.ts
  • __tests__/hooks/configure-wizard.test.ts
  • __tests__/hooks/delivery-health.test.ts
  • __tests__/hooks/manager.test.ts
  • __tests__/hooks/policy-attribution.test.ts
  • __tests__/hooks/policy-evaluator.test.ts
  • __tests__/hooks/policy-presets.test.ts
  • bin/failproofai.mjs
  • crates/failproofaid/src/cloud_client.rs
  • crates/failproofaid/src/main.rs
  • crates/fpai-collect/src/sources/hermes/mod.rs
  • crates/fpai-collect/src/sources/hermes/transform.rs
  • crates/fpai-collect/tests/hermes_source.rs
  • package.json
  • src/audit/audit-schedule.ts
  • src/hooks/cloud-connection.ts
  • src/hooks/cloud-enrollment-cli.ts
  • src/hooks/cloud-enrollment.ts
  • src/hooks/configure-wizard.ts
  • src/hooks/delivery-health.ts
  • src/hooks/enforcement-capability.ts
  • src/hooks/fp-config.ts
  • src/hooks/handler.ts
  • src/hooks/policy-evaluator.ts
  • src/hooks/policy-presets.ts

Comment thread CHANGELOG.md
Comment thread crates/failproofaid/src/cloud_client.rs
Comment thread src/hooks/configure-wizard.ts
Comment thread src/hooks/delivery-health.ts
Comment thread src/hooks/delivery-health.ts
Comment thread src/hooks/handler.ts
@hermes-exosphere

hermes-exosphere commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Hermes

Status Reviewed
Verdict Changes requested
Head 3242b91f4d9c
Rounds 1 of 5

The PR fixes the prior default-Claude fallback, but Recommended setup can now finish with no hooks at all when no supported CLI is detected. Static parsing of modified TypeScript passed in an isolated container; dependency-backed tests could not run because the isolated container has no cached packages and network access is disabled.

What this changes

flowchart LR
    n0Setupwizard["~ Setup wizard"]
    n1Policypresets["~ Policy presets"]
    n2Hookenforcement["~ Hook enforcement"]
    n3Deliveryhealthreporting["+ Delivery health reporting"]
    n4Cloudpolicydaemon["~ Cloud policy daemon"]
    n5Hermescollector["~ Hermes collector"]
    n0Setupwizard -- "selects recommended policies" --> n1Policypresets
    n0Setupwizard -- "installs CLI hook configs" --> n2Hookenforcement
    n0Setupwizard -- "installs daemon service" --> n4Cloudpolicydaemon
    n3Deliveryhealthreporting -- "emits SessionStart warnings" --> n2Hookenforcement
    n4Cloudpolicydaemon -- "supplies managed policies" --> n2Hookenforcement
    n5Hermescollector -- "ships session batches" --> n3Deliveryhealthreporting
Loading

Rounds

Round Reviewed Commits in this round Verdict
0 a7b5eaef16b6 10edea80561c 83949d908ffb 79907c4cef51 8a43d8b53306 b3159f8ec491 cb21e42f57ea a7b5eaef16b6 Approved
1 3242b91f4d9c 25eced7848e9 3242b91f4d9c Changes requested — F1

Findings

Open

  • F1 Recommended setup silently applies no protection when no CLI is detected (src/hooks/configure-wizard.ts) — round 1

@hermes-exosphere help lists every command. This comment is maintained in place — I rewrite it after each review rather than posting a new one.

@hermes-exosphere hermes-exosphere left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hermes found no blocking issues in this revision.

1 advisory finding
  • Medium/High Recommended setup falls back to Claude when no CLI was detected — recommendedClis is derived solely from detectInstalledClis() and is assigned to clisSel at src/hooks/configure-wizard.ts:1260. If detection returns [], the resulting empty clis array is passed to installHooks; its existing default at src/hooks/manager.ts:167 interprets an empty array as ["claude"]. Thus choosing Recommended on a machine with no detected CLI still creates/configures Claude hooks, contrary to the stated detected-CLIs-only behavior. (src/hooks/configure-wizard.ts:1260)

chhhee10 and others added 2 commits August 12, 2026 16:56
Two e2e tests still asserted `hookSpecificOutput.additionalContext` on a
PostToolUse deny for codex and copilot — the shape the previous commit removed
for exactly those two CLIs, because neither reads it. They belonged in that
commit and are red on CI without them.

The shared `assertPostToolUseDeny` is left alone. Sixteen of its eighteen call
sites are claude, which does read the nested shape and is unaffected, so
widening the helper to accept either form would have made every one of those
sites pass on the wrong shape as well.

The new assertion also requires `hookSpecificOutput` to be ABSENT rather than
merely unread. Emitting both shapes would satisfy a looser check while leaving
the file asserting a contract no consumer exercises — and the whole defect this
fixes was a response that looked correct and was read by nobody.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The cloud screen's second line read "No key? Create one at …". That phrases the
situation as a missing artefact, which lands wrong on the person it is for:
somebody seeing this screen on a first run has not mislaid a key, they have
never heard of the product, and "No key?" reads to them as an error state
rather than an invitation. It now names the product — "New to FailproofAI?
Create a key at https://befailproof.ai/get-started/" — which is self-selecting:
a reader with a key skips it, and a reader without one is told what to do.

The destination is unchanged and still deliberate: get-started rather than the
dashboard host, because a person reading this line usually has no org either
and get-started issues both.

Also resolves the twelve `(#PR)` placeholders in the 1.0.0 section to #683.
The seventy-four elsewhere in the file belong to other PRs and are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@hermes-exosphere hermes-exosphere left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hermes found blocking issues that should be addressed.

High: Recommended setup silently applies no protection when no CLI is detected

  • Rule: COR-001
  • Location: src/hooks/configure-wizard.ts:1260
  • Evidence: recommendedClis comes only from detectInstalledClis() (src/hooks/configure-wizard.ts:1122-1124). Recommended mode assigns that list directly to clisSel (line 1260), while the apply loop calls installHooks only when clisForScope.length > 0 (line 1791). With no detected supported CLI, the wizard still completes and marks the daemon configured, but installs no hooks and never persists the recommended policy set.
  • Required change: Do not permit a successful Recommended apply with an empty detected-CLI list: direct the user to Customize/select a supported CLI, or require a detected CLI before continuing. Add a regression test for zero detected CLIs.

Comment thread src/hooks/configure-wizard.ts
@NiveditJain
NiveditJain merged commit 67d4332 into main Aug 12, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants