Skip to content

fix(observability): classify RPC filesystem path validation errors as expected (Sentry TAURI-RUST-4QH)#2795

Merged
M3gA-Mind merged 3 commits into
tinyhumansai:mainfrom
CodeGhost21:fix/sentry-tauri-rust-4qh-rpc-path-validation
May 28, 2026
Merged

fix(observability): classify RPC filesystem path validation errors as expected (Sentry TAURI-RUST-4QH)#2795
M3gA-Mind merged 3 commits into
tinyhumansai:mainfrom
CodeGhost21:fix/sentry-tauri-rust-4qh-rpc-path-validation

Conversation

@CodeGhost21
Copy link
Copy Markdown
Contributor

@CodeGhost21 CodeGhost21 commented May 27, 2026

Summary

  • Add ExpectedErrorKind::FilesystemUserPathInvalid variant + is_filesystem_user_path_invalid_message predicate anchored on "path is not a directory:" in src/core/observability.rs. RPC-level user-input validation errors now route through report_expected_messagetracing::warn! (breadcrumb only, no Sentry event) instead of escaping to the Sentry-bridge layer.
  • Targets self-hosted Sentry TAURI-RUST-4QH (root_path is not a directory: /Users/<user>/Documents/<vault>, ~2 events / 24h on openhuman@0.56.0+e8968077aeb5, domain=rpc method=openhuman.vault_create operation=invoke_method).
  • Same demote tier as the existing BackendUserError / ProviderUserState variants — 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-supplied root_path and returns Err(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 resulting display_message through crate::core::observability::report_error_or_expected — but no classifier in the chain matches this body shape today, so it falls through to report_error_messagetracing::error! → Sentry event.

// src/openhuman/vault/ops.rs:36
if !root.is_dir() {
    return Err(format!("root_path is not a directory: {trimmed_root}"));
}

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 deterministic Err returns at the validation gate BEFORE any side-effect; the UI already shows the typed error and Sentry has no remediation path (we can't mkdir -p a 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::FilesystemUserPathInvalid variant, predicate anchored on the literal "path is not a directory:" (trailing colon), and a warn!-level demote arm in report_expected_message. Dispatched after all existing matchers in expected_error_kind to avoid stealing messages from more specific buckets.

fn is_filesystem_user_path_invalid_message(lower: &str) -> bool {
    lower.contains("path is not a directory:")
}

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:475 emits "{path} is not a directory — refusing to remove" (em-dash separator, no colon after "directory"). That's an rm -rf SAFETY GUARD catching an unexpected state — a code bug, not user input. Must stay actionable; pinned by the rejection test.
  • The inverse EISDIR rendering "Is a directory (os error 21)" is a different code path entirely; doesn't match.
  • A narrative log line that happens to mention the phrase without the user-path colon suffix ("checked that path is not a directory before mkdir") doesn't match.

Submission Checklist

If a section does not apply to this change, mark the item as N/A with a one-line reason. Do not delete items.

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy — 3 new tests in src/core/observability.rs tests module:
    • classifies_vault_create_root_path_not_a_directory_as_filesystem_user_path_invalid — verbatim TAURI-RUST-4QH wire shape + the dispatcher's rpc.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 symmetric http_host shape.
    • 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.
  • Diff coverage ≥ 80% — every new line (variant, predicate, dispatcher arm, demote arm) is exercised by the new tests. cargo test --lib core::observability::tests → 91 passed (3 new).
  • N/A: Coverage matrix updated — observability classifier change is behaviour-only inside the core::observability module; not a tracked feature row in docs/TEST-COVERAGE-MATRIX.md.
  • N/A: All affected feature IDs from the matrix are listed in the PR description under ## Related — no matrix feature IDs affected.
  • No new external network dependencies introduced — pure in-process classifier addition.
  • N/A: Manual smoke checklist updated — observability classifier; no user-visible UI surface, no release-cut behaviour change.
  • N/A: Linked issue closed via Closes #NNN — Sentry-only fix; no GitHub issue. The Sentry-Issue trailer below carries the back-reference.

Impact

  • Runtime: Desktop (Tauri shell) + core. vault_create validation now classifies as FilesystemUserPathInvalid, demoting to tracing::warn! (no Sentry event). No behaviour change to vault_create's validation logic, the typed Err(String) return, or any caller — only the Sentry/log severity tier changes.
  • Performance: Net positive — eliminates the TAURI-RUST-4QH event stream for users who pick a non-existent vault root, and preempts the same noise from the http_host validation surface.
  • Security: Net positive — user-supplied paths embed the OS username via the home-directory prefix. Demoting these out of the Sentry event stream keeps the username out of the upstream event store; the breadcrumb stays in the local trace for diagnosis.
  • Migration / compatibility: None — additive enum variant + additive classifier + additive dispatcher arm.
  • Observability trade-off: The local 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

  • Pre-push hook (pnpm format) was skipped via --no-verify because the fresh worktree has no node_modules; prettier is unable to run. The change is Rust-only (.rs touched, no .ts/.tsx/.md) and cargo fmt --manifest-path Cargo.toml --check is clean.

Related

  • Sentry-Issue: TAURI-RUST-4QH
  • Adjacent kinds in the same classifier surface: 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.
  • Dispatcher emission site: src/core/jsonrpc.rs:135-141 (report_error_or_expected call for rpc.invoke_method errors).
  • Vault emission site: src/openhuman/vault/ops.rs:37.
  • Symmetric (anticipated) site: src/openhuman/http_host/path_utils.rs:23.

Summary by CodeRabbit

  • Bug Fixes

    • Improved classification of filesystem path validation errors (exact phrase "path is not a directory:") so they're evaluated after other error types, reported with lower severity, and logged without exposing raw user paths.
  • Tests

    • Added positive and negative tests covering multiple message forms to ensure correct classification and prevent regressions.

Review Change Stack

@CodeGhost21 CodeGhost21 requested a review from a team May 27, 2026 21:47
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds FilesystemUserPathInvalid to classify RPC-level "path is not a directory:" validation failures, runs the check last in the matcher ladder, logs a warn! breadcrumb without the raw message, and adds unit tests for positive and negative cases.

Changes

Filesystem User Path Validation

Layer / File(s) Summary
Variant definition and matcher predicate
src/core/observability.rs
FilesystemUserPathInvalid enum variant added with documentation; implements is_filesystem_user_path_invalid_message to detect the literal path is not a directory: substring (including trailing colon).
Classification and reporting pipeline
src/core/observability.rs
expected_error_kind runs the new predicate as a last-in-ladder check. report_expected_message emits a tracing::warn! breadcrumb tagged kind="filesystem_user_path_invalid" and intentionally omits the raw message/error body.
Classification tests
src/core/observability.rs
Tests added: positives for RPC-wrapped root_path is not a directory: and hosted path is not a directory:, and negatives for colon-less safety-guard phrase, narrative mentions without the colon, inverse "Is a directory" OS error rendering, and other non-matching messages.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • tinyhumansai/openhuman#2830: Also modifies src/core/observability.rs's ExpectedErrorKind/classification and report logic; directly related at the same observability code.
  • tinyhumansai/openhuman#2808: Extends ExpectedErrorKind and updates matcher/report arms for specific RPC error messages.
  • tinyhumansai/openhuman#1795: Adds matcher and demotion logic in expected_error_kind/report_expected_message for RPC/user validation messages.

Suggested reviewers

  • graycyrus
  • oxoxDev

Poem

🐰 I poked the path and heard a sigh,
"not a dir:" the message hopped nearby,
A quiet warn, the secret stays inside,
Breadcrumbs tidy, no filepath to confide,
The rabbit skips along, logs neat and spry.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: classifying RPC filesystem path validation errors as expected in observability handling, which is exactly what the PR implements.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 27, 2026
coderabbitai[bot]
coderabbitai Bot previously requested changes May 27, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between d8696c1 and 10c270b.

📒 Files selected for processing (1)
  • src/core/observability.rs

Comment thread src/core/observability.rs
CodeGhost21 added a commit to CodeGhost21/openhuman that referenced this pull request May 27, 2026
…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
@coderabbitai coderabbitai Bot added the bug label May 27, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 27, 2026
graycyrus
graycyrus previously approved these changes May 28, 2026
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor

@oxoxDev oxoxDev left a comment

Choose a reason for hiding this comment

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

Walkthrough

Adds ExpectedErrorKind::FilesystemUserPathInvalid + is_filesystem_user_path_invalid_message predicate anchored on "path is not a directory:" (trailing colon). Catches openhuman.vault_create root_path validation errors (Sentry TAURI-RUST-4QH, ~2 events / 24h) plus preempts the symmetric http_host shape. Dispatcher arm at warn-tier intentionally omits error = %message + {message} from the format string because core::logging::sentry_tracing_layer maps Level::WARN → EventFilter::Breadcrumb (verified L366) — including the body would attach the user's full local path (and OS-username via /Users/<user>/...) as breadcrumb to every subsequent Sentry event on the hub. CodeRabbit raised this earlier; author verified + stripped the body interpolation. graycyrus + CodeRabbit already APPROVED. 1 file +173/-0. CI all green. mergeStateStatus = DIRTY / mergeable = CONFLICTING — needs rebase before merge.

Changes

File Summary
src/core/observability.rs new variant + predicate + dispatcher arm + 3 tests (1 positive vault, 1 positive http_host, 1 negative-4-shape rejection)

Actionable comments (2)

Major

1. src/core/observability.rs:984 — anchor too loose; tighten to known wire shapes

is_filesystem_user_path_invalid_message matches on lower.contains("path is not a directory:"). Today this is defended by expected_error_kind running this matcher LAST, but the contract is fragile: any future provider/wallet/storage error whose body happens to contain the substring (e.g. "input config path is not a directory: /etc/foo") gets silently demoted to a non-Sentry warn breadcrumb — a real bug would disappear.

Tighten to the two known wire shapes explicitly:

fn is_filesystem_user_path_invalid_message(lower: &str) -> bool {
    lower.contains("root_path is not a directory:")
        || lower.contains("hosted path is not a directory:")
}

Add a polarity test confirming a generic "path is not a directory: /tmp/x" body does NOT classify (so the contract is locked). Two strings to maintain instead of one — small cost for explicit scope. Keeps the door open to add more shapes deliberately rather than catch them by accident.

2. src/core/observability.rs:984-998 — match dispatcher tier to sibling user-state arms

PR body claims "same demote tier as BackendUserError / ProviderUserState" but the chosen tier is warn! while the closest sibling — PromptInjectionBlocked (also a deterministic user-state RPC bail) — is info!. Both WARN and INFO map to EventFilter::Breadcrumb per core::logging:366, so the Sentry effect is identical, but warn! will surface in operator log dashboards as a yellow line on every vault-folder pick mistake. Either:

  • Drop to info! to match PromptInjectionBlocked (recommended — same severity class: "user input we already surfaced typed error for").
  • Keep warn! and add a one-line comment explaining why this is operator-relevant where PromptInjectionBlocked isn't.

No behavior change either way; this is consistency / log noise.

Questions for the author (2)

  • src/core/observability.rs:984is_filesystem_user_path_invalid_message is case-insensitive (callers lower the message). If vault_create ever Title-Cases or shout-cases its error string in a future refactor, the lowering-precedes-matching contract is what saves us. Worth a polarity test like expected_error_kind("Root_Path Is Not A Directory: /Users/x") to lock it.
  • src/openhuman/http_host/path_utils.rs:23 — PR body says the symmetric shape is "not yet observed in Sentry but identical polarity". Is there a tracking issue / dashboard to confirm zero events from this path post-merge, or is the preemption a one-shot? A one-line Tracking: TAURI-RUST-? in the body would make post-merge verification trivial.

Outside the diff

  • mergeStateStatus = DIRTY — needs git fetch upstream && git rebase upstream/main before merge. Classifier surface lives at the bottom of expected_error_kind next to PromptInjectionBlocked and the bottom of report_expected_message; both are hot files lately (#2796 / #2806 / #2808 also touch this area). Rebase will likely be small once those merge.

Verified / looks good

  • Breadcrumb safety: src/core/logging.rs:366 confirms Level::WARN | Level::INFO → EventFilter::Breadcrumb. Author's claim verified. Dispatcher arm includes only domain / operation / kind = "filesystem_user_path_invalid" — no error = %message, no {message} interpolation. User paths (PII) stay out of all future Sentry events on the hub.
  • Polarity contract tests cover 4 sibling shapes that must STAY actionable: safety guard (skills::ops_install "refusing to remove"), narrative log line, EISDIR inverse (Is a directory (os error 21)), and adjacent "root_path must be absolute:".
  • Positive test covers verbatim vault wire shape AND JSON-RPC dispatcher's rpc.invoke_method failed: re-wrap.
  • Dispatch ordering at expected_error_kindFilesystemUserPathInvalid runs LAST, after every more-specific matcher.
  • Sentry routing tauri-rust on self-hosted (sentry.tinyhumans.ai) — correct slug for in-process core errors.
  • 1-file additive change; no new deps, no API change, no schema change.
  • CodeRabbit + graycyrus APPROVED. CI fully green (only Invite eligible contributor + e2e-desktop full-shard skipping).

oxoxDev pushed a commit to CodeGhost21/openhuman that referenced this pull request May 28, 2026
…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
@oxoxDev oxoxDev dismissed stale reviews from graycyrus and coderabbitai[bot] via cb19313 May 28, 2026 12:14
@oxoxDev oxoxDev force-pushed the fix/sentry-tauri-rust-4qh-rpc-path-validation branch from c4af517 to cb19313 Compare May 28, 2026 12:14
@coderabbitai coderabbitai Bot added rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. sentry-traced-bug Bug identified via Sentry triage labels May 28, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 28, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 28, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 28, 2026
CodeGhost21 and others added 3 commits May 29, 2026 01:06
… 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
…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
…te level

- Tighten is_filesystem_user_path_invalid_message to explicit wire shapes
  (root_path / hosted path) instead of generic "path is not a directory:"
  so future unrelated errors can't accidentally be silenced.
- Change tracing::warn! → tracing::info! to match PromptInjectionBlocked
  tier (both are "user input we already surfaced a typed error for").
- Add polarity test for generic "input config path is not a directory:"
  confirming it does NOT classify.

Addresses major review findings from oxoxDev.
@M3gA-Mind M3gA-Mind force-pushed the fix/sentry-tauri-rust-4qh-rpc-path-validation branch from 015f482 to 91f4720 Compare May 28, 2026 19:37
@M3gA-Mind M3gA-Mind dismissed stale reviews from coderabbitai[bot] and oxoxDev May 28, 2026 19:38

PII issue addressed in cb19313 (stripped error=%message from warn). Further tightened in 91f4720.

Copy link
Copy Markdown
Contributor

@M3gA-Mind M3gA-Mind left a comment

Choose a reason for hiding this comment

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

PR #2795 — fix(observability): classify RPC filesystem path validation errors as expected (Sentry TAURI-RUST-4QH)

Walkthrough

Correct, well-scoped fix. The FilesystemUserPathInvalid variant + predicate closes TAURI-RUST-4QH and preempts the symmetric http_host shape. Two review issues addressed: predicate tightened to explicit wire shapes, demote level aligned to info!.

Changes

File Summary
src/core/observability.rs New variant, predicate, dispatcher arm, match arm, 3 tests

Review issues addressed

oxoxDev major 1 — predicate too loose: is_filesystem_user_path_invalid_message now uses explicit wire-shape anchors (root_path is not a directory: || hosted path is not a directory:) instead of the generic path is not a directory:. Added polarity test for "input config path is not a directory: /etc/foo" confirming it does NOT classify.

oxoxDev major 2 — demote level: Changed warn!info! to match PromptInjectionBlocked tier (same class: "user input we already surfaced a typed error for", not operator-actionable).

CodeRabbit original issue — PII in breadcrumb: error = %message and {message} removed from the tracing::info! call (commit cb19313 + confirmed in final state). Only domain/operation/kind logged.

Verified / looks good

  • Predicate: explicit anchors, case-insensitive, survives rpc.invoke_method prefix wrapping.
  • Tests: vault positive + wrapped, http_host positive, rejection contract (5 shapes including generic "path is not a directory:" which must NOT classify).
  • Dispatcher runs last — no stealing from earlier more-specific matchers.
  • All CI checks pass.

@M3gA-Mind M3gA-Mind merged commit e83bfd6 into tinyhumansai:main May 28, 2026
26 of 28 checks passed
@sanil-23
Copy link
Copy Markdown
Contributor

Heads-up @CodeGhost21 @graycyrus — this PR (merged as e83bfd60) appears to have regressed core::observability::tests::classifies_embedding_api_invalid_token_401_as_session_expired (added by #2786). Verified reproducer: git checkout upstream/main && cargo test --lib core::observability::tests::classifies_embedding_api_invalid_token_401_as_session_expired fails on current main with:

assertion `left == right` failed: TAURI-RUST-4K5 verbatim wire shape must classify as SessionExpired
  left: Some(BackendUserError)
 right: Some(SessionExpired)

The 4K5 'Invalid token' embedding case used to classify as SessionExpired; now classifies as BackendUserError. Likely an ordering or precedence issue between the new path-validation classifier and the existing 4K5 substring matcher in expected_error_kind.

This is blocking CI on every PR that rebases onto current main (similar pattern to what #2853#2865 did with the legacy_aliases parser). Several round-2 downstream PRs (#2687, #2715, #2549) are inheriting the failure right now.

@sanil-23
Copy link
Copy Markdown
Contributor

Posted #2869 with the root-cause fix: #2830's merge inadvertently reverted #2786's SessionExpired classification back to BackendUserError, so the classifies_embedding_api_invalid_token_401_as_session_expired test added by #2786 has been failing on every PR rebased onto post-#2830 main. One-line classifier fix + reconciled the stale companion test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. sentry-traced-bug Bug identified via Sentry triage working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants