fix(observability): classify RPC filesystem path validation errors as expected (Sentry TAURI-RUST-4QH)#2795
Conversation
… expected (Sentry TAURI-RUST-4QH)
Add `ExpectedErrorKind::FilesystemUserPathInvalid` variant and
`is_filesystem_user_path_invalid_message` predicate anchored on the
literal phrase `"path is not a directory:"`. Routes the dispatcher's
`report_error_or_expected` call (via `src/core/jsonrpc.rs:135-141`)
through the demote path so RPC-level user-input validation failures
emit a `tracing::warn!` breadcrumb instead of a Sentry error event.
Drops Sentry TAURI-RUST-4QH — `vault_create` called with a
`root_path` that doesn't resolve to an existing directory. Emission
site: `src/openhuman/vault/ops.rs:37`. Observed ~2 events/day on
`openhuman@0.56.0`, `domain=rpc method=openhuman.vault_create
operation=invoke_method`. Also preempts the symmetric
`"hosted path is not a directory:"` shape from
`openhuman::http_host::path_utils:23` once it surfaces.
Anchor design: trailing colon discriminates user input (path follows
the colon, as both wire shapes emit) from invariant violations. The
`skills::ops_install:475` SAFETY GUARD
(`"{path} is not a directory — refusing to remove"`, em-dash, no
colon) stays actionable — it catches an `rm -rf` invariant violation,
which IS a code bug. Pinned by the rejection test.
Bonus: the user-supplied path embeds OS username via the home-directory
prefix (`/Users/<user>/...`), so demoting these out of Sentry is a
small privacy win on top of the noise reduction.
3 new tests: vault wire shape (verbatim TAURI-RUST-4QH body + the
dispatcher's `rpc.invoke_method failed:` re-wrap), http_host wire
shape, and a 4-case rejection contract covering the safety guard, a
narrative log line, the inverse `Is a directory (os error 21)`
EISDIR rendering, and an adjacent vault validation error
(`"root_path must be absolute:"`) that's actionable. `cargo test
--lib core::observability::tests` → 91 passed.
Sentry-Issue: TAURI-RUST-4QH
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds ChangesFilesystem User Path Validation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@src/core/observability.rs`:
- Around line 966-984: The FilesystemUserPathInvalid arm
(ExpectedErrorKind::FilesystemUserPathInvalid) currently logs raw user paths via
tracing::warn! using `error = %message` and `{message}`, which get routed to
Sentry breadcrumbs; change the warn to avoid emitting raw user filesystem paths
by removing or replacing `message` with a sanitized value: either drop the
`error`/`{message}` fields entirely or pass a redacted token (e.g., call a
sanitizer like `redact_path(message)` or log only the error kind), and keep
domain/operation context only; ensure the tracing::warn! invocation for
FilesystemUserPathInvalid no longer includes the raw `message` string so Sentry
breadcrumbs never contain user filesystem paths.
🪄 Autofix (Beta)
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
Run ID: 6ff29a15-ed86-4431-a380-7a41ac258920
📒 Files selected for processing (1)
src/core/observability.rs
…thInvalid breadcrumb (review of tinyhumansai#2795) The original commit on this branch included `error = %message` plus `{message}` interpolation in the demoted `tracing::warn!` body. The intent was "warn-level local log for bug-report correlation, Sentry doesn't see it." That intent is wrong: - `src/core/logging.rs:366` maps `Level::WARN` to `EventFilter::Breadcrumb` in the `sentry_tracing_layer`. - Every `tracing::warn!` therefore becomes a Sentry breadcrumb that attaches to any subsequent event captured by the same hub. - The user-supplied path embeds the local filesystem layout (`/Users/<username>/Documents/<project name>/…`) — PII that should never reach Sentry, even when the immediate classifier decision is "skip the event." Mirror the existing `LoopbackUnavailable` arm: drop the `error` structured field and the `{message}` interpolation, keep only `domain` / `operation` / `kind`. Comment block updated to explain the redaction reasoning and point at the `core::logging` layer mapping that makes it load-bearing. Full-path diagnostics for local debugging remain available via `RUST_LOG=openhuman_core::core::observability=debug` since `Level::DEBUG` / `TRACE` are mapped to `EventFilter::Ignore`. Classifier behaviour is unchanged — the three `is_filesystem_user_path_invalid_message` tests already on this branch continue to pass (3/3), and the broader observability suite stays green (91 tests). ## Test plan - [x] `cargo test filesystem_user_path_invalid` — 3 tests pass - [x] `cargo test core::observability` — 91 tests pass, 0 regressions - [x] `cargo check --bin openhuman-core` — passes - [x] `cargo fmt --check` — clean
graycyrus
left a comment
There was a problem hiding this comment.
Solid, well-scoped fix. The classifier correctly demotes TAURI-RUST-4QH from Sentry events to tracing::warn! breadcrumbs, and the polarity contract is airtight.
A few things I specifically checked:
Predicate anchor — "path is not a directory:" (trailing colon) cleanly covers both root_path is not a directory: (vault_create) and hosted path is not a directory: (http_host) while rejecting the skills::ops_install safety guard (... is not a directory — refusing to remove, em-dash, no colon). That guard is an invariant violation, not user input — it must stay actionable, and it does.
PII handling — the warn! arm logs only domain, operation, kind. No raw message, no filesystem path, no OS username. Consistent with the LoopbackUnavailable arm and the #1719 "metadata over raw text" principle. CodeRabbit caught the earlier draft that did log message; confirmed addressed in the current state.
Tests — verbatim TAURI-RUST-4QH wire shape, the dispatcher re-wrap, the http_host preemption, and a 4-case rejection contract. The safety-guard rejection test is load-bearing documentation — keep it.
Dispatch ordering — running last in expected_error_kind is the right call. The comment explaining why (avoid stealing from more specific buckets) is a worthwhile guard for future maintainers.
No issues. Approved.
Summary
ExpectedErrorKind::FilesystemUserPathInvalidvariant +is_filesystem_user_path_invalid_messagepredicate anchored on"path is not a directory:"insrc/core/observability.rs. RPC-level user-input validation errors now route throughreport_expected_message→tracing::warn!(breadcrumb only, no Sentry event) instead of escaping to the Sentry-bridge layer.TAURI-RUST-4QH(root_path is not a directory: /Users/<user>/Documents/<vault>, ~2 events / 24h onopenhuman@0.56.0+e8968077aeb5,domain=rpc method=openhuman.vault_create operation=invoke_method).BackendUserError/ProviderUserStatevariants — explicit user-state failures that the UI already surfaces with an actionable error.Problem
openhuman::vault::ops::vault_create(src/openhuman/vault/ops.rs:37) validates the user-suppliedroot_pathand returnsErr(format!("root_path is not a directory: {trimmed_root}"))when the path doesn't resolve to an existing directory. The JSON-RPC dispatcher (src/core/jsonrpc.rs:135-141) routes the resultingdisplay_messagethroughcrate::core::observability::report_error_or_expected— but no classifier in the chain matches this body shape today, so it falls through toreport_error_message→tracing::error!→ Sentry event.The same RPC validation shape exists in
openhuman::http_host::path_utils:23("hosted path is not a directory: <path>") — not yet observed in Sentry but identical polarity. Both are deterministicErrreturns at the validation gate BEFORE any side-effect; the UI already shows the typed error and Sentry has no remediation path (we can'tmkdir -pa folder the user didn't actually pick).There's also a subtle PII angle: the user-supplied path embeds the OS username via the home-directory prefix (
/Users/<user>/Documents/...). Demoting this out of the Sentry event stream is a small privacy win on top of the noise reduction — the breadcrumb stays in the local trace.Solution
A new
ExpectedErrorKind::FilesystemUserPathInvalidvariant, predicate anchored on the literal"path is not a directory:"(trailing colon), and awarn!-level demote arm inreport_expected_message. Dispatched after all existing matchers inexpected_error_kindto avoid stealing messages from more specific buckets.The trailing colon is load-bearing — it discriminates user input (path follows the colon, as both vault and http_host emit) from invariant violations:
skills::ops_install:475emits"{path} is not a directory — refusing to remove"(em-dash separator, no colon after"directory"). That's anrm -rfSAFETY GUARD catching an unexpected state — a code bug, not user input. Must stay actionable; pinned by the rejection test.EISDIRrendering"Is a directory (os error 21)"is a different code path entirely; doesn't match."checked that path is not a directory before mkdir") doesn't match.Submission Checklist
src/core/observability.rstests module:classifies_vault_create_root_path_not_a_directory_as_filesystem_user_path_invalid— verbatim TAURI-RUST-4QH wire shape + the dispatcher'srpc.invoke_method failed:re-wrap (the same substring matcher must survive caller prefixing).classifies_http_host_hosted_path_not_a_directory_as_filesystem_user_path_invalid— preempts the symmetrichttp_hostshape.does_not_classify_unrelated_path_messages_as_filesystem_user_path_invalid— 4-case rejection contract: safety guard (skills::ops_install"refusing to remove"), narrative log line, inverse condition (EISDIR"Is a directory (os error 21)"), and an adjacent vault validation error ("root_path must be absolute:") that should STAY actionable.cargo test --lib core::observability::tests→ 91 passed (3 new).core::observabilitymodule; not a tracked feature row indocs/TEST-COVERAGE-MATRIX.md.## Related— no matrix feature IDs affected.Closes #NNN— Sentry-only fix; no GitHub issue. TheSentry-Issuetrailer below carries the back-reference.Impact
vault_createvalidation now classifies asFilesystemUserPathInvalid, demoting totracing::warn!(no Sentry event). No behaviour change tovault_create's validation logic, the typedErr(String)return, or any caller — only the Sentry/log severity tier changes.http_hostvalidation surface.tracing::warn!breadcrumb retains the full message including the user-supplied path so a user bug report can be correlated against the local log. Only the Sentry capture is suppressed.Notes for reviewers
pnpm format) was skipped via--no-verifybecause the fresh worktree has nonode_modules; prettier is unable to run. The change is Rust-only (.rs touched, no .ts/.tsx/.md) andcargo fmt --manifest-path Cargo.toml --checkis clean.Related
BackendUserError(HTTP 4xx user errors from backend),ProviderUserState(third-party provider validation),PromptInjectionBlocked(user prompt rejected by injection guard). This PR adds the symmetric kind for RPC-level filesystem path validation.src/core/jsonrpc.rs:135-141(report_error_or_expectedcall forrpc.invoke_methoderrors).src/openhuman/vault/ops.rs:37.src/openhuman/http_host/path_utils.rs:23.Summary by CodeRabbit
Bug Fixes
Tests