Skip to content

docs: correct interceptor execution order in CLAUDE.md - #575

Merged
atilafassina merged 1 commit into
mainfrom
fix/interceptor-order-discrepancy
Sep 7, 2026
Merged

docs: correct interceptor execution order in CLAUDE.md#575
atilafassina merged 1 commit into
mainfrom
fix/interceptor-order-discrepancy

Conversation

@atilafassina

Copy link
Copy Markdown
Contributor

What

Corrects the Execution Interceptor Pattern section of the root CLAUDE.md, which documented the interceptor nesting in the exact reverse of the runtime behavior, plus several wrong keys in the config example.

Why

The doc claimed the order was Telemetry (outermost) → Timeout → Retry → Cache (innermost). The actual runtime nesting is the reverse:

  • _buildInterceptors (packages/appkit/src/plugin/plugin.ts) pushes the array as [telemetry, timeout, retry, cache].
  • _executeWithInterceptors folds that array so each later entry becomes the inner wrapper.
  • Net effect, outermost → innermost: Cache → Retry → Timeout → Telemetry → fn().

The in-code comment at plugin.ts:700 (telemetry → timeout → retry → cache (innermost to outermost)) was already correct — only CLAUDE.md was stale.

Changes

  • Flip the interceptor order list to match runtime (Cache outermost → Telemetry innermost), with a one-line note on how the array fold produces it.
  • Add the layered-observability nuance: the innermost plugin.execute span doesn't wrap cache hits/retries/timeouts, but the cache layer emits its own cache.getOrExecute span + hit/miss metrics, and connectors carry their own spans; timeout/retry only log.
  • Fix the config example:
    • Wrap config in { default: ... } (PluginExecutionSettings) — the real execute() signature.
    • retry: { maxRetries: 3 }retry: { enabled: true, attempts: 3 } (RetryConfig.attempts).
    • cache.ttl is in seconds, and cache needs enabled + cacheKey to activate.
    • telemetry: { traces: true }telemetryInterceptor: { enabled: true } (the per-call key; traces is plugin-level config).

Docs-only change — no code touched.

Note / follow-up

There's a separate, deliberate design question left for a follow-up: whether the code should make TelemetryInterceptor outermost (so the span covers retries/timeout/cache-hit). Not done here — it changes the interceptor composition and trades away the pure-work-latency signal, so it wants its own PR + owner sign-off. Recommended alternative if pursued: an outer operation span plus the existing inner per-attempt span.

This pull request and its description were written by Isaac.

The documented interceptor order was the reverse of the runtime nesting.
_buildInterceptors pushes [telemetry, timeout, retry, cache] and
_executeWithInterceptors folds the array so each later entry becomes the
inner wrapper, netting outermost-to-innermost: Cache -> Retry -> Timeout
-> Telemetry -> fn().

Also fix the config example: it must be wrapped in { default: ... }
(PluginExecutionSettings), retry uses 'attempts' not 'maxRetries', cache
'ttl' is in seconds, cache/retry need 'enabled' to activate, and the
per-call telemetry key is 'telemetryInterceptor' (not 'telemetry: { traces }').
Add the layered-observability nuance (cache layer + connectors carry their
own spans; timeout/retry only log).

Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: Atila Fassina <atila@fassina.eu>
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

🤖 AppKit PR bot

🔬 Run evals

Start an eval for this PR from the evals-monitor app: Go to Evals Monitor →

📦 Try this PR's app template

Scaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh auth login — and the Databricks CLI):

gh run download 33861774533 -R databricks/appkit -n appkit-template-0.71.0-pr.f444d6a-fix-interceptor-order-discrepancy-575 -D appkit-pr-575 \
  && unzip -o "appkit-pr-575/appkit-template-0.71.0-pr.f444d6a-fix-interceptor-order-discrepancy-575.zip" -d "appkit-pr-575" \
  && databricks apps init --template "appkit-pr-575"

The template pins @databricks/appkit and @databricks/appkit-ui to tarballs built from this branch, so the scaffolded app runs against this PR's code.

@atilafassina
atilafassina marked this pull request as ready for review September 7, 2026 06:31
@atilafassina
atilafassina requested a review from a team as a code owner September 7, 2026 06:31
@atilafassina
atilafassina requested review from calvarjorge and a lite review from Copilot September 7, 2026 06:31
@atilafassina
atilafassina merged commit 9dc312d into main Sep 7, 2026
10 checks passed
@atilafassina
atilafassina deleted the fix/interceptor-order-discrepancy branch September 7, 2026 06:31

Copilot AI 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.

🟡 Changes recommended

The updated docs contain a couple of factual inaccuracies (the wrapper-fold description and a non-existent file path) that should be corrected before merging.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR updates the root CLAUDE.md documentation to accurately describe AppKit’s interceptor nesting order at runtime and corrects the per-call execute() configuration example to match the actual PluginExecutionSettings/PluginExecuteConfig shape used by the SDK.

Changes:

  • Correct interceptor nesting order to match runtime behavior (Cache outermost → Telemetry innermost).
  • Add an “observability is layered” note describing which layers emit spans vs. logs/metrics.
  • Fix the execute() config example keys/structure (e.g., { default: ... }, retry.attempts, cache.ttl units, telemetryInterceptor).
File summaries
File Description
CLAUDE.md Fixes interceptor-order and execution-config documentation to match the actual runtime composition and config types.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread CLAUDE.md
3. **TimeoutInterceptor** - AbortSignal timeout
4. **TelemetryInterceptor** (innermost) - Traces the execution span, then `fn()` runs

`_buildInterceptors` pushes them in the array order `[telemetry, timeout, retry, cache]`, then `_executeWithInterceptors` folds that array so each *later* entry becomes the *inner* wrapper — netting the cache-outermost order above.
Comment thread CLAUDE.md

**Observability is layered — it's not just the one `plugin.execute` span:**
- The innermost `TelemetryInterceptor` opens `plugin.execute`, but because it sits *below* cache/retry/timeout: a cache hit never reaches it, retry attempts do not share a single parent span, and a timeout surfaces only as a generic "Operation cancelled by client" (indistinguishable from a user cancel).
- The **cache layer** (`src/cache/index.ts`) opens its own `cache.getOrExecute` span with a `cache.hit` attribute plus hit/miss metrics and structured logs — so cache hits *are* observable, via that layer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants