Source profileQuality 91/100Review permissions

JoviDeCroock/pracht/skills/add-observability/SKILL.md

add-observability

Wire Sentry or OpenTelemetry into pracht server boundaries (loaders, middleware, API routes), client-side Web Vitals reporting, and a capability audit sink that records every capability dispatch with transport, outcome, latency, and verified identity. Use for "add observability", "wire Sentry", "set up tracing", "add OpenTelemetry", "monitor Web Vitals", "track errors", "log capability calls", or "are agents calling my app".

Source repository stars
94
Declared platforms
0
Static risk flags
2
Last source update
2026-08-28
Source checked
2026-08-28

Decision brief

What it does: where it fits

Four layers, each opt-in:

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/JoviDeCroock/pracht --skill "skills/add-observability"
    Safe inspection promptEditorial

    Inspect the Agent Skill "add-observability" from https://github.com/JoviDeCroock/pracht/blob/43a8e8dcccb137abcc7c78d4a238edc01829e5a9/skills/add-observability/SKILL.md at commit 43a8e8dcccb137abcc7c78d4a238edc01829e5a9. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      Step 1: Pick the stack

      The skill below shows Sentry and OTel patterns. Custom beacon is mentioned but trivial.

      Sentry — easiest end-to-end (errors + traces + Web Vitals).OpenTelemetry + your backend (Honeycomb, Grafana, Datadog, Jaeger).Custom beacon — minimal fetch('/api/telemetry') setup, no SaaS.
    2. 02

      Step 2: Server error tracking

      The pattern below uses @sentry/node and works on the Node adapter only — see the caveat box below for Cloudflare and Vercel Edge before installing anything.

      The pattern below uses @sentry/node and works on the Node adapter only — see the caveat box below for Cloudflare and Vercel Edge before installing anything.Create src/server/observability.ts:Add a global middleware that calls initObservability() once (from inside the handler, never at module scope) and wraps the downstream call:
    3. 03

      Step 3: Loader/API tracing

      For each loader and API handler, wrap the body in a span.

      For each loader and API handler, wrap the body in a span.Auto-injection is out of scope; provide a snippet, recommend wrapping the 5-10 slowest loaders (cross-reference with audit-bundles perf hotspots).
    4. 04

      Step 4: Web Vitals on the client

      Create src/client/vitals.ts — export a function, no module-level side effects:

      Create src/client/vitals.ts — export a function, no module-level side effects:Do NOT import this statically from a shell: shells render on the server too, so module-level onCLS(...) calls would execute during SSR. The primary pattern is a lazy import() inside an effect, guarded by useIsHydrated (…This keeps web-vitals out of the critical bundle (lazy chunk) and only starts observers after hydration has fully settled.
    5. 05

      Step 5: Beacon endpoint

      For Sentry users, Sentry's browser SDK can capture Web Vitals natively — prefer that over a custom beacon if you've gone the Sentry route.

      For Sentry users, Sentry's browser SDK can capture Web Vitals natively — prefer that over a custom beacon if you've gone the Sentry route.

    Permission review

    Static risk signals and limitations

    Network access

    medium · line 24

    The documentation includes network, browsing, or remote request actions.

    **Custom beacon** — minimal `fetch('/api/telemetry')` setup, no SaaS.

    Runs scripts

    medium · line 38

    The documentation asks the agent to run terminal commands or scripts.

    pnpm add @sentry/node # Node adapter only

    Network access

    medium · line 89

    The documentation includes network, browsing, or remote request actions.

    middleware and `fetch` them to Sentry's store/envelope endpoint (or any

    Runs scripts

    medium · line 125

    The documentation asks the agent to run terminal commands or scripts.

    pnpm add @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/auto-instrumentations-node

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score91/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars94SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    JoviDeCroock/pracht
    Skill path
    skills/add-observability/SKILL.md
    Commit
    43a8e8dcccb137abcc7c78d4a238edc01829e5a9
    License
    MIT
    Collected
    2026-08-28
    Default branch
    main
    View the original SKILL.md

    Pracht Add Observability

    Four layers, each opt-in:

    1. Server error tracking — capture loader/middleware/API exceptions.
    2. Request tracing — span per request with child spans per loader/db call.
    3. Web Vitals (LCP/CLS/INP/FCP/TTFB) — client-side, posted to a beacon endpoint.
    4. Agent traffic — one structured audit event per capability dispatch. Only relevant when the app registers capabilities; skip it otherwise.

    MCP: when the pracht MCP server is registered (docs/MCP.md), prefer its inspect_agents/inspect_routes/inspect_api/inspect_build/doctor/ verify/generate_* tools over shelling out. pracht inspect needs the pracht plugin in the vite config.

    Step 1: Pick the stack

    Use AskUserQuestion:

    • Sentry — easiest end-to-end (errors + traces + Web Vitals).
    • OpenTelemetry + your backend (Honeycomb, Grafana, Datadog, Jaeger).
    • Custom beacon — minimal fetch('/api/telemetry') setup, no SaaS.

    The skill below shows Sentry and OTel patterns. Custom beacon is mentioned but trivial.

    Step 2: Server error tracking

    Sentry path (Node adapter)

    The pattern below uses @sentry/node and works on the Node adapter only — see the caveat box below for Cloudflare and Vercel Edge before installing anything.

    pnpm add @sentry/node    # Node adapter only
    

    Create src/server/observability.ts:

    import { serverEnv } from "@pracht/core/env/server";
    import * as Sentry from "@sentry/node";
    
    let initialized = false;
    export function initObservability() {
      if (initialized) return;
      initialized = true;
      // serverEnv is read INSIDE this function, not at module scope — module-level
      // env reads break on runtimes where env arrives per request (docs/ENV.md).
      Sentry.init({
        dsn: serverEnv.SENTRY_DSN,
        tracesSampleRate: Number(serverEnv.SENTRY_TRACES_SAMPLE_RATE ?? 0.1),
        environment: serverEnv.NODE_ENV,
      });
    }
    

    Add a global middleware that calls initObservability() once (from inside the handler, never at module scope) and wraps the downstream call:

    // src/middleware/observability.ts
    import type { MiddlewareFn } from "@pracht/core";
    import * as Sentry from "@sentry/node";
    import { initObservability } from "../server/observability";
    
    export const middleware: MiddlewareFn = async ({ request, route }, next) => {
      initObservability();
      return Sentry.startSpan(
        {
          name: `${request.method} ${route.path}`,
          op: "http.server",
        },
        () => next(),
      );
    };
    

    Cloudflare / Vercel Edge caveat — be honest here. @sentry/cloudflare requires wrapping the worker's fetch handler with withSentry(), but pracht's Cloudflare adapter owns that handler — there is no user hook to wrap it today, so the middleware-init pattern above cannot work with @sentry/cloudflare, and @sentry/node does not run on Workers at all. Do not scaffold a pattern that can't work. Options on Cloudflare:

    1. Plain fetch-based event forwarding: catch errors in a wrap-around middleware and fetch them to Sentry's store/envelope endpoint (or any HTTP sink) yourself. Read the DSN via serverEnv inside the middleware.
    2. Wait for pracht to expose a handler-wrap hook for the adapter, then use Sentry's Cloudflare SDK properly.

    The same applies to @sentry/vercel-edge: verify how the init hooks into the runtime before installing; if it needs to own the handler, fall back to option 1. (This mirrors the OTel-edge honesty note below.)

    Pracht middleware is wrap-around: await next() invokes the rest of the request and resolves to the final Response, so the span naturally covers the loader/handler and ends when they finish.

    Register it in defineApp({ middleware: { observability: "./..." } }) (the top-level middleware field is a registry keyed by name — not an ordered chain). To actually wrap requests, place "observability" first in every chain that should cover them:

    defineApp({
      middleware: { observability: "./middleware/observability.ts", auth: "./middleware/auth.ts" },
      api: { middleware: ["observability"] },           // all API routes
      routes: [
        group({ middleware: ["observability"] }, [      // all pages
          group({ middleware: ["auth"] }, [ /* protected routes */ ]),
        ]),
      ],
    });
    

    Ordering lives in these middleware: [...] arrays — always place observability first so it spans the rest of the chain.

    OpenTelemetry path

    pnpm add @opentelemetry/api @opentelemetry/sdk-trace-node @opentelemetry/auto-instrumentations-node
    

    Create a SDK init module that runs at server entry — for Node, use the --require ./otel.cjs flag; for Cloudflare/Vercel edge, OTel is more limited (use HTTP exporter directly). Surface this trade-off; don't pretend OTel edge is plug-and-play.

    Step 3: Loader/API tracing

    For each loader and API handler, wrap the body in a span.

    import * as Sentry from "@sentry/node";
    
    export async function loader({ request }) {
      return Sentry.startSpan({ name: "loader: dashboard", op: "function" }, async () => {
        return { /* ... */ };
      });
    }
    

    Auto-injection is out of scope; provide a snippet, recommend wrapping the 5-10 slowest loaders (cross-reference with audit-bundles perf hotspots).

    Step 4: Web Vitals on the client

    pnpm add web-vitals
    

    Create src/client/vitals.ts — export a function, no module-level side effects:

    import { onCLS, onINP, onLCP, onFCP, onTTFB, type Metric } from "web-vitals";
    
    function send(metric: Metric) {
      navigator.sendBeacon?.(
        "/api/telemetry/vitals",
        JSON.stringify({ name: metric.name, value: metric.value, id: metric.id, path: location.pathname }),
      );
    }
    
    export function reportVitals() {
      onCLS(send);
      onINP(send);
      onLCP(send);
      onFCP(send);
      onTTFB(send);
    }
    

    Do NOT import this statically from a shell: shells render on the server too, so module-level onCLS(...) calls would execute during SSR. The primary pattern is a lazy import() inside an effect, guarded by useIsHydrated (exported from @pracht/core), placed in a shell or top-level component:

    import { useIsHydrated } from "@pracht/core";
    import { useEffect } from "preact/hooks";
    
    export function Vitals() {
      const hydrated = useIsHydrated();
      useEffect(() => {
        if (!hydrated) return;
        void import("../client/vitals").then((m) => m.reportVitals());
      }, [hydrated]);
      return null;
    }
    

    This keeps web-vitals out of the critical bundle (lazy chunk) and only starts observers after hydration has fully settled.

    Step 5: Beacon endpoint

    // src/api/telemetry/vitals.ts
    import type { ApiRouteArgs } from "@pracht/core";
    
    export async function POST({ request }: ApiRouteArgs) {
      const body = await request.text();
      // Forward to your destination (Sentry, Honeycomb, custom store).
      // Keep body small; do not block on the upstream.
      console.log("vitals", body);
      return new Response(null, { status: 204 });
    }
    

    For Sentry users, Sentry's browser SDK can capture Web Vitals natively — prefer that over a custom beacon if you've gone the Sentry route.

    Step 6: Agent traffic (capability apps only)

    Skip when pracht inspect agents --json reports an empty capabilities list. That only means there are no capability operations to audit; llms.txt, MCP, or Web Bot Auth may still expose other agent-facing surfaces. When capabilities do exist, every dispatch already emits a structured CapabilityAuditEvent — nothing is instrumented per capability, a sink just has to be registered.

    import { addCapabilityAuditListener } from "@pracht/core/server";
    
    const stopAuditLog = addCapabilityAuditListener("audit-log", (event) => {
      console.log(
        JSON.stringify({
          msg: "capability",
          at: new Date().toISOString(),
          capability: event.capability,
          effect: event.effect,
          transport: event.transport, // "http" | "webmcp" | "mcp" | "server"
          via: event.via, // causal transport for nested invokeCapability()
          outcome: event.outcome, // "ok" or the envelope error code
          status: event.status,
          durationMs: Math.round(event.durationMs),
          agent: event.agent?.agentDomain ?? event.agent?.keyId ?? null,
        }),
      );
    });
    
    if (import.meta.hot) {
      import.meta.hot.dispose(stopAuditLog);
    }
    

    The OTel version records a counter and a histogram keyed on capability/transport/outcome, and backdates a span with startTime: Date.now() - event.durationMs (the dispatch has already finished when the sink runs). The full snippet is on the agent-trust docs page.

    Import the module from an eagerly loaded server module. The adapter's configured createContextFrom module is one portable option: add import "./audit.ts" there so the generated entry registers the sink before request handling. A custom server entry can import it directly. Do not rely on an unrelated route, API route, middleware, or src/server/ registry module; those modules are lazy and can miss earlier capability calls.

    Key properties to state when scaffolding this:

    • Sinks are invoked synchronously, so keep work before the callback returns or reaches its first await cheap. A returned promise is never awaited, and a synchronous throw is swallowed (first failure per sink reported via console.warn, naming it), so a broken exporter cannot fail the call.
    • Always pass a stable name as the first argument. Registering the same name again replaces that sink, which is what keeps a module-scope call safe under dev HMR: Vite re-executes importers on every save, so an unkeyed registration would add a fresh closure per keystroke and deliver every event N times. Never compute the name. Register the returned unsubscribe with import.meta.hot.dispose() too, so removing the module or renaming the sink cannot leave the old name active until the dev server restarts.
    • setCapabilityAuditHook() is a single slot — a second call replaces the first. Use addCapabilityAuditListener() whenever more than one sink exists; it returns an unsubscribe handle that removes only its own registration.
    • Warning suppression is per named registration, so differently named sinks still report independently when they reuse one callback.
    • Delivery snapshots the registered sinks before callbacks run. Adding or replacing a sink from inside a callback takes effect on the next dispatch, so the current event is never delivered twice to one name.
    • On Cloudflare Workers, a batching exporter must flush within the request or be handed the execution context by app code (context.executionContext.waitUntil(exporter.flush())). Pracht does not call ctx.waitUntil() for a sink.
    • Audit events cover dispatch only. A cross-origin 403, an unknown-capability 404, and an unknown MCP tool name all return before dispatch and emit nothing, so do not build a reconnaissance alert on these events — use the HTTP access log for that.

    In dev the same events are already collected: the Agents section of /_pracht shows recent dispatches with transport, via, verified identity, outcome, and duration, and /_pracht.json exposes all of them under agentTraffic. The page counts verified identities, MCP, and MCP-caused composition as agent-attributed; shows top-level unsigned HTTP, HTTP-caused composition, and client-declared WebMCP markers separately as unverified client dispatches; and hides only invokeCapability() work with no served-request provenance behind a first-party toggle, so the panel's visible count can be lower than the sink's. Counts and empty-state conclusions only cover the retained window when older events have been dropped. Use it to confirm the sink sees what the panel sees before wiring a paid backend. Adapter-owned dev servers do not register this middleware: on Cloudflare workerd, /_pracht and /_pracht.json return 404. Validate the sink from its own output there; a missing panel is not a failed audit hook.

    Step 7: Sampling and PII

    • Set SENTRY_TRACES_SAMPLE_RATE to a small number (0.05–0.10) in production.
    • Scrub auth headers and cookies from breadcrumbs:
      Sentry.init({ beforeSend(event) { delete event.request?.headers?.cookie; return event; } });
      
    • Never send loader return values verbatim — they often contain user data.

    Step 8: Verify

    • Trigger a deliberate error in dev and confirm it lands in Sentry/OTel.
    • Open a route, check the Web Vitals beacon fires (Network tab).
    • If an audit sink was added: call a capability in dev and confirm the event reaches both the sink and, when the adapter exposes it, the Agents panel at /_pracht. On Cloudflare's adapter-owned dev server, validate the sink output directly because the panel does not exist.
    • Confirm pnpm test and pnpm e2e still pass.
    • Run pracht typegen if any routes were added (the beacon API route does not affect page-route types, but re-run when in doubt).
    • Run pracht verify --json and confirm no failures.

    Rules

    1. Confirm adapter compatibility before installing the SDK package (Sentry has separate packages per runtime), and never scaffold a pattern the runtime can't actually run — see the Cloudflare/Vercel-edge caveat in Step 2. Read SENTRY_DSN and friends via serverEnv inside functions, never process.env at module scope.
    2. Top-level middleware in defineApp is a name→path registry, not an ordered chain. Place "observability" first in every group({ middleware: [...] }) and in api.middleware so it wraps the rest.
    3. Web Vitals only matter for SSR/SSG/ISG routes that hydrate; SPA-only routes still benefit but the values reflect the post-bootstrap state.
    4. Sample traces (≤ 10%) in production; full sampling in dev.
    5. Never send raw cookies, auth headers, or full loader payloads to a third-party SaaS. The same applies to audit events: log the capability name, effect, transport, outcome, and agent domain — not the capability's input, which is application data.
    6. Keep the synchronous part of an audit sink cheap: no CPU-heavy work or synchronous network call. Returned promises are fire-and-forget; the runtime does not await them or catch their rejections.

    $ARGUMENTS

    Frequently asked questions

    What to verify before installation and use

    What does the add-observability source document cover?

    Four layers, each opt-in:

    How do I install add-observability?

    The source record exposes this install command: npx skills add https://github.com/JoviDeCroock/pracht --skill "skills/add-observability". Inspect the command and pinned source before running it.

    Which permission-related actions were detected?

    Static rules flagged network, exec-script in the source; the page lists the matching lines and excerpts.

    Alternatives

    Compare before choosing

    Computed 10029,236

    garrytan/gbrain

    bulk-ingestion

    End-to-end discipline for turning any large data source (audio libraries, email takeouts, document corpora, chat exports, API dumps) into brain pages at scale. The lifecycle spine: SCHEMA → ACCESS → TRIAL → EVALUATE → IMPROVE → CODIFY → TEST → SKILLIFY → BULK → MONITOR. State is tracked in a durable JSON manifest (see MANIFEST-PATTERN.md) so any crash, session boundary, or subagent fan-out resumes from ground truth instead of memory.

    Computed 10025,136

    alirezarezvani/claude-skills

    app-store-optimization

    App Store Optimization (ASO) toolkit for researching keywords, analyzing competitor rankings, generating metadata suggestions, and improving app visibility on Apple App Store and Google Play Store. Use when the user asks about ASO, app store rankings, app metadata, app titles and descriptions, app store listings, app visibility, or mobile app marketing on iOS or Android. Supports keyword research and scoring, competitor keyword analysis, metadata optimization, A/B test planning, launch checklist

    Computed 1005,277

    dotnet/skills

    migrate-vstest-to-mtp

    Migrates .NET test projects from VSTest to Microsoft.Testing.Platform (MTP). Use when user asks to "migrate to MTP", "switch from VSTest", "enable Microsoft.Testing.Platform", "use MTP runner", set OutputType=Exe only for test projects in Directory.Build.props, or mentions EnableMSTestRunner, EnableNUnitRunner, or UseMicrosoftTestingPlatformRunner. USE FOR: MTP behavioral differences vs VSTest (exit code 8, zero tests discovered, --ignore-exit-code, TESTINGPLATFORM_EXITCODE_IGNORE); centralizing

    Computed 100147

    oaustegard/claude-skills

    featuring

    Generate hierarchical _FEATURES.md files that describe what a codebase DOES from a user/consumer perspective, anchored to source symbols via tree-sitting. Supports large complex codebases through feature-driven decomposition into sub-feature files. Uses a multi-pass synthesis: orientation → detail → overview rewrite. Use when someone says "what does this do", "document features", "feature inventory", "_FEATURES.md", or needs to understand a codebase's purpose before modifying it. Complements tre