* fix: validate Azure blob storage container names
Azure requires container names to be 3-63 chars, lowercase alphanumeric
and hyphens only. Add Zod superRefine validation to the form schema,
tRPC router, and public API schema so invalid names like "Feedback N8N Bot"
are rejected at submission time with a clear error message.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review feedback for Azure container name validation
- Add empty-string guard in validateAzureContainerName to avoid double
error when bucketName is blank
- Add .min(1) to public API bucketName schema to match tRPC form schema
- Add Fern docs note describing Azure container naming constraints
- Add server test for invalid Azure container name rejection
- Add client test for empty-string guard behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(web): Table padding issues
* Increase cell padding in `SelectDashboardDialog` and `SelectWidgetDialog`
* Fix memoization comparison for cellPadding in DataTable
* Set cellPadding="comfortable" for `MembersTable` in org settings
* fix(web): allow unicode letters in signup name validation
* refactor(web): share name schema between signup and display name
* fix(web): enforce 100-char limit in shared name schema
* fix(web): allow hyphens, apostrophes, and periods in name validation
The nameSchema regex was too strict, rejecting common name characters
like O'Brien, Smith-Jones, and Dr. Smith. Also align the backend
updateDisplayName schema with the shared nameSchema for consistency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(web): normalize smart quotes and require letter in name validation
Normalize curly/smart apostrophes (U+2018, U+2019, U+02BC) from mobile
autocorrect to straight apostrophe before validation. Require at least
one letter to reject degenerate punctuation-only names like "---".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(web): require base letter not combining mark in name validation
The "must contain at least one letter" refine accepted standalone
combining marks (\p{M}) without an actual letter (\p{L}), allowing
inputs like "\u0301\u0301" to pass as valid names.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(web): require base letter not combining mark in name validation
Add NFC normalization before validation so decomposed characters merge
into precomposed form, and add a negative lookahead (?!\p{M}) to reject
names that still start with a combining mark after normalization.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(web): revert display name form to permissive schema and simplify nameSchema
Revert settings and userAccount display name validation back to
StringNoHTML.min(1).max(100) — the signup-oriented nameSchema is too
restrictive for existing display names containing underscores, ampersands, etc.
Simplify nameSchema: merge transforms, combine regex constraints into a single
refine that requires names start with a letter, and remove U+02BC from
smart-quote normalization (it's a linguistic letter, not a typographic quote).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: make Slack integration more robust
* refactor: deduplicate scopes
* fix(slack): make SlackChannel isPrivate and isMember optional
These fields are only known for channels from the fetched list, not for
manually-typed channel names. Making them optional avoids placeholder
booleans and fixes a type error when constructing partial channel objects.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: gracefully handle missing scopes
* fix(slack): cap rate-limit retry, resolve manual channel IDs, add empty state
- Cap retryAfter to 60s max to avoid gateway timeouts on large Slack values
- Add onSuccess handler in SlackActionForm to resolve #channel names to real IDs
- Show empty state message in ChannelSelector when bot has no accessible channels
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(slack): resolve manual channel IDs, virtualize list, fix audit log
- Use resolved Slack channel ID in audit log instead of #-prefixed input
- Replace VirtualizedList with cmdk Command + @tanstack/react-virtual
for keyboard navigation and DOM-efficient rendering of ~5k channels
- Move "Use typed name" fallback to separate CommandGroup so it stays
visible when the virtualized group has zero height
- Import SlackChannel type from @langfuse/shared instead of redeclaring
- Add getChannelInfo mock and #-prefixed channelId test
- Use .concat() instead of spread for channel pagination (repo convention)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: switch to SDK retry policies
* fix(slack): strip duplicate # prefix and use functional setState
Strip leading # from channelId fallback in test message block to avoid
displaying ##general for manually-typed channel names. Use functional
setSelectedChannel form in slack.tsx to match SlackActionForm.tsx pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(slack): guard CommandEmpty on filteredChannels length
Prevent flash of "No channels available." on popover open by explicitly
guarding CommandEmpty rendering on filteredChannels.length === 0 instead
of relying on cmdk's internal item count, which is 0 on the first
render before the virtualizer scroll container mounts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: review comments
* chore: another round of review feedback
* chore: more review comments
* fix(slack): improve channel selector search
* fix comment
* fix(slack): refine channel selector search
* fix(slack): sync manifest scopes
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(codex): install golang-migrate in docker setup
* fix(codex): include local bin path in maintenance
* fix(codex): load local bin path in setup shell
* fix(codex): verify migrate checksum and safe extract
* fix(codex): improve migrate install error guidance
* chore(dx): add more stop for pre-commit
* chore: add type checking as well
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: add auto-fixed files to diff
* chore: change to not modifying / remove typecheck
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* put prompt_* and tool_* behind 'includeIO'
* ordering
* put prompt outside of io
* also omit for events
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* fix(prompt): webhook triggers honor eventAction filters
* fix(automations): added validation of event actions
* test(automations): add deleted event action test case to promptVersionProcessor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Revert "fix(automations): added validation of event actions"
This reverts commit 210344f49b45fd80e79fecf6841dd37dbe822a28.
* fix(test): update setupTriggerAndAction to match all event actions
The helper used eventActions: ["updated"] which broke the prompt
creation test after eventActions filtering was enforced. Using []
matches all actions, covering both created and updated test cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(ci): retrigger stuck license cla check
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(api): return archived dataset items from GET endpoint
Previously GET /api/public/dataset-items/{id} returned 404 for archived
items. Now it returns them with their status, matching user expectations
for direct ID lookups.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(web): Improve search highlighting in CodeMirrorEditor
* Add color variables
* Always call `syncEditorsToQuery` if the active changed
* make selector more spefific
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* ci(sdk): replace poetry with uv in SDK API spec generation workflow
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ci: add frozen flag
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(scores): make `TEXT` scores available via public API
* fix(scores): address review feedback for TEXT score public API
- Remove TEXT example from v1 docs to match CORRECTION pattern
- Split PostScoresBody into v1 (excludes TEXT) and v2 (includes TEXT)
- Fix v2 schema to keep value required for non-TEXT types using
per-branch extend instead of weakening the foundation schema
- Migrate merge() to extend() across validation schemas
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(scores): address second round of review feedback
- Use local TextData without length constraints in v2 response schema
- Add CreateScoreDataTypeV1 enum in Fern to exclude TEXT from v1 POST
- Use function overloads in convertScoreToPublicApi for proper typing
- Fix misleading comment about v2 POST endpoint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(scores): support TEXT scores in v1 API
TEXT scores are now fully supported across both v1 and v2 APIs for
create, list, and get-by-id. Only CORRECTION remains v2-only. Uses
LISTABLE_SCORE_TYPES instead of AGGREGATABLE_SCORE_TYPES for v1 filtering.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(scores): include TEXT in CreateScoreValue docs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(scores): update error message and Fern inline docs to mention TEXT scores
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(shared): use literal tuple for LISTABLE_SCORE_TYPES to narrow type
Array.filter() without a type predicate infers ScoreDataTypeType[],
so ListableScoreDataType incorrectly included CORRECTION at the type
level. Define as a literal tuple with `as const` to match the pattern
used by AGGREGATABLE_SCORE_TYPES.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(web): spread readonly LISTABLE_SCORE_TYPES to satisfy mutable array type
The `as const` readonly tuple was incompatible with the mutable array
parameter expected by `useSidebarFilterState`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(shared): use TEXT_SCORE_MAX_LENGTH global constant in API and ingestion schemas
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(web): route rollback errors to correct form field for TEXT scores
The rollback error handlers unconditionally set errors on the `.value`
field, but TEXT scores render their `<FormMessage>` on `.stringValue`.
This caused server errors to be silently dropped for TEXT annotations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(web): fix type errors in rollback error field routing for TEXT scores
Use if/else branches instead of ternary to preserve template literal
types for react-hook-form's setError and clearErrors field paths.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: introduce free form scores
* chore: implement review comments
* chore: rename to `Text` score
* chore: review feedback
* chore: fix exposing scores in traces/observation view, export as well as prompts
* style: polishing for long values
* chore: fix CI issues
* fix(shared,worker): propagate TEXT score data type in export streams and session aggregation
- Extend ClickHouse tuples to include data_type as third element in
buildScoresAggregationCTE, observation-stream, and trace-stream
- Read actual data_type from tuple instead of hardcoding CATEGORICAL
in event-stream, observation-stream, and trace-stream
- Include TEXT scores in eventsSessionScoresAggregation filter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: review
* fix(shared): preserve TEXT data type when inflating ingested scores
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(shared): extract TEXT score max length into global constant
Replace hardcoded max length (500) for TEXT scores with a shared
TEXT_SCORE_MAX_LENGTH constant for reuse across ingestion and public API.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(shared): address PR review comments for TEXT score type
- Rename misleading test names to match actual assertions (TEXT is included)
- Use ListableScoreDataType return type for getScoresGroupedByNameSourceType
- Replace hardcoded maxLength={500} with TEXT_SCORE_MAX_LENGTH constant
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(shared): widen score types to include TEXT in prompt scores and ScoreSimplified
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: last review comment
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
perf(worker): advance experiment backfill cursor per chunk and add query timeouts
Previously the backfill cursor only advanced after ALL chunks succeeded.
If any chunk failed, the entire window was retried from scratch—causing
chunk 1 to re-run (with duplicate writes) and the same failing chunk to
block progress indefinitely.
Now the cursor advances after each successful chunk (items ordered ASC),
so on retry only the remaining chunks are processed. Also adds explicit
60s query timeouts to getRelevantObservations and getRelevantTraces, and
reduces the default chunk size from 200 to 100 for smaller blast radius.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* perf(worker): skip redundant IngestionService enrichment in experiment backfill
Spans processed by the experiment backfill already have model match,
usage details, cost details, and pricing tier data from ClickHouse.
Running them through IngestionService.createEventRecord() redundantly
re-does model matching (Redis + Postgres), tokenization, and cost
calculation. Convert EnrichedSpan directly to EventRecordInsertType
and write to ClickHouse, removing the IngestionService dependency.
Refs: LFE-9149
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore drop unused await
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds LANGFUSE_EXPERIMENT_BACKFILL_EXCLUDE_PROJECT_IDS env var (comma-separated)
to filter out specific projects after fetching eligible dataset run items,
preventing them from being processed in the experiment dual-write backfill.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(otel): add defensive logging for bad cost/usage details and
oversized spans
* chore: last ditch logging when entire batch fails
* chore: tests for malformed _details handling
* perf(worker): optimize experiment backfill query and add delay metrics
Replace the broad events_core table scan with a CTE-driven approach that
first identifies candidate DRIs in the time window, then uses that small
set to drive the anti-join. This avoids scanning the full events_core
history.
Add two gauges to track backfill cursor delay so drift is detected early
rather than accumulating silently over months.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* perf(worker): add upper time bound to observation and trace queries
Adds a maxTime upper bound (chunkEnd + 7 days) to the getRelevantObservations
and getRelevantTraces queries so ClickHouse scans a bounded time range instead
of everything from minTime to now.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(worker): disable experiment backfill in event propagation queue
Temporarily disables the experiment backfill step in the event propagation
processor to address performance issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(worker): cap experiment backfill to 8-hour windows and re-enable
The backfill was disabled because a stale Redis timestamp caused unbounded
query windows (e.g. 5+ weeks). Each execution now processes at most 8 hours
of data, letting the scheduler catch up incrementally across runs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a 2-minute request timeout to the getDatasetRunItemsSinceLastRun
ClickHouse query to prevent long-running queries from hanging indefinitely.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(tables-ui): support full text search targeting input or output
* feat(tests): add search functionality tests for generations, traces, and dataset items by input and output
* fix(search): use import type for TracingSearchType
Fix ESLint warning by using type-only import for TracingSearchType
since it's only used as a type annotation, not a runtime value.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(ui): add visual indicator to Full Text submenu when selected
- Add dot indicator next to Full Text submenu trigger when any child option is selected
- Matches existing pattern used for IDs/Names radio item
- Indicator appears for all full-text search modes (content, input, output)
* chore: build
* chore: build
* chore: build
* fix(prompts): enhance search functionality to include tag matching
* fix(tests): correct dataset items search test to use proper API signature
- Update test to use filterState instead of filter parameter
- Change offset to page parameter
- Use createDatasetItemFilterState helper for proper filter construction
- Fixes TypeError: Cannot read properties of undefined (reading 'map')
* fix(tests): use unique dataset name to avoid constraint conflicts
- Change hardcoded dataset name to v4() for uniqueness
- Prevents Unique constraint failed error when running full test suite
* test:generations
* docs: wording
* make faster
* fix: after rebase
* fix: imports
* fix: update searchType defaults in dataset items and prompt router
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* feat(blob-export): add more fields to v3 and v4 blob storage exports
* feat(blob-export): add model pricing enrichment and missing fields to
v3/v4 exports
* ci: pin all GitHub Actions to commit SHAs
Pin all third-party GitHub Actions to their full commit SHAs for
supply-chain security, with the version tag preserved as a comment.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ci: add dependabot config for grouped weekly GitHub Actions updates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
During a Redis outage, commands hang indefinitely because ioredis has no
commandTimeout, no socketTimeout, and enableOfflineQueue defaults to true.
This causes cascading latency across the application.
- Add REDIS_COMMAND_TIMEOUT (2s default) for singleton and rate limiter
- Add REDIS_REQUEST_SOCKET_TIMEOUT_MS (5s default) for request/response connections
- Add REDIS_BLOCKING_SOCKET_TIMEOUT_MS (30s default) for all connections including
BullMQ workers (safe because BZPOPMIN returns every ~5s drain delay)
- Centralize enableOfflineQueue: false in defaultRedisOptions, removing duplication
from 34 queue files
- Fix cluster mode to forward enableOfflineQueue to top-level ClusterOptions
(previously silently ignored in redisOptions)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
feat(billing): add universal $4K default spend alert for all plans
Adds a $4000 spend alert on top of the existing plan-specific default
alerts on new subscriptions, as requested in LFE-8154 to help catch
unintentional high-spend loads across all plan tiers.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Migrate workflows to Blacksmith
* wait for up
* make redis IPs play well with blacksmith
---------
Co-authored-by: blacksmith-sh[bot] <157653362+blacksmith-sh[bot]@users.noreply.github.com>
Co-authored-by: Nimar <l.nimar.b@gmail.com>
Fixes a race condition where adding a new filter row in the dashboard
widget form would immediately be overwritten before the user could
interact with it. The useEffect had wipFilterState in its dependency
array, causing it to re-run on every filter interaction; combined with
onChange being called inside _setWipFilterState's updater, this could
produce a stale hasWipFilters=false snapshot that reset the WIP state.
Applies the same prevFilterStateRef pattern already used in
PopoverFilterBuilder: bail out early when filterState reference hasn't
changed, and read current WIP state via functional updater to avoid
the race.
Closes#12569
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* feat(exports): decode unicode escapes in batch export pipeline
Apply decodeUnicodeEscapesOnly() to the batch export stringify functions
so that \uXXXX sequences (produced by Python SDK's json.dumps with
ensure_ascii=True) are decoded to their original characters in exported
CSV/JSON/JSONL files.
This follows the same approach already used in the Web UI (PR #9686)
where decodeUnicodeEscapesOnly() was added to IOTableCell for display.
Closes#10972
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review feedback - move unicode.ts to shared, use greedy mode
- Move decodeUnicodeEscapesOnly to shared package and re-export from web
- Use greedy=true to match Web UI behavior (IOTableCell.tsx)
- Export from @langfuse/shared main index for Jest compatibility
* refactor: add early return for perf, fix test import path
- Add indexOf('\\') early return in decodeUnicodeEscapesOnly for fast
path when no backslashes present
- Export stringify/stringifyForCsv from transforms barrel
- Use @langfuse/shared/src/server import in tests instead of relative path
* fix: add lone-surrogate guard in greedy mode
In greedy mode, when tryDecodeSurrogatePair fails due to double-escaped
backslashes, lone surrogates were emitted via String.fromCharCode(),
producing WTF-16 strings that corrupt to U+FFFD on UTF-8 write.
Fix: add greedy-aware surrogate pair decoding that skips extra backslashes,
and preserve lone surrogates as literal \uXXXX text (same as non-greedy mode).
Added tests for lone high/low surrogates in greedy mode.
* test: add backslash-between-surrogates edge case test in greedy mode
* minimize test cases
* preserve
* skip
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* refactor(web): unify experiments access hook and beta switch component
* feat(web): add beta page placeholders on dataset views
* chore: show new UI in dataset-run page routes
* chore: propagate to all pages
* fix(sessions): fix position in trace to default to 1st
* add test
* add presets
* root
* refactor position in trace a bit
* test
* new test
* fix type
* type
* feat(prompts): add duplicate folder action and tests
Adds folder-level prompt duplication in prompts UI and tRPC, including nested path handling and single/all-version copy modes. This enables teams to clone prompt hierarchies while preserving webhook trigger behavior for copied prompts
* rewrite prompt references
* add text to clarify behaviour when copying only latest + refrences
* escape
* add test
* text
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* fix(web): use pointer cursor for enabled buttons
* fix(web): mark disabled onboarding buttons as aria-disabled
* fix(web): tighten pointer cursor and keyboard semantics
* feat(ui): migrate support form
* push
* push
* code clean up
* metadata, first response
* fix
* only write emails with langfuse CH to Pylon
* error toast if sending message fails
* add langfuse plan to issue and account
---------
Co-authored-by: Marc Klingen <2834609+marcklingen@users.noreply.github.com>
* fix(playground): prioritize cmd+enter run-all shortcut
* fix(playground): use cmd+enter only on mac
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* feat: allow LLM-as-a-judge to filter by tool names and tool call count
* chore: remove mention of events table
* style: simplify
* chore: fix direct instantations of `ObservationForEval`
* chore: address review comments
* chore: review feedback
* chore: add calledToolNames to columnsWithCustomSelect
Allow users to type custom tool names in the eval filter dropdown,
consistent with how tags and name filters work.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Revert "style(web): remove resize cursor for non resizable sidebar (#12761)"
This reverts commit 5150609b38.
* style(web): replace resize cursor with pointer on SidebarRail
The rail is a toggle button, not a resize handle.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: review comments
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ci(worker): add vitest config for IDE test discovery
Add vitest.config.ts and vitest.workspace.ts so the Vitest VS Code
extension can discover worker tests. Load ../.env via dotenv to match
the CLI setup and inline @langfuse/shared for correct module resolution.
Fix vi.mock hoisting errors by wrapping mock variables in vi.hoisted()
in 4 test files where top-level variables were referenced inside
vi.mock factories.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(web): add run experiment dialog on experiments page
* fix(web): refresh experiments table after creating run
* fix(web): show sdk run options on experiments dialog
* perf(prisma): add index on job_executions.job_configuration_id
Speeds up cascade deletes when removing an LLM-as-a-judge evaluator
by indexing the foreign-key lookup on jobConfigurationId.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(db): add IF NOT EXISTS to concurrent index migration
Makes the migration idempotent so retries after partial failures
(e.g., index created but Prisma completion record not written) don't
block deployments with "already exists" errors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: improve widget loading states for small dashboards
* Remove Codex artifacts and ignore generated previews
* Add tight chart loading state and indeterminate query progress
* fix(web): remove fake widget form query progress
* chore(web): format loading state components
* refactor(web): make SidebarRail a non-interactive div
The sidebar rail showed resize cursors and a hover accent bar despite
not supporting drag-to-resize. Replace the button with a plain div since
the SidebarTrigger in the page header already handles toggling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style(web): remove orphaned after:left-full class from SidebarRail
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(web): remove unused SidebarRail component
The rail only existed as a click-to-toggle hit target. Now that it is
no longer interactive, the invisible div serves no purpose.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add gzip compression option for blob storage integration exports
Add opt-in gzip compression for blob storage integration exports.
When enabled, exported files use .csv.gz/.json.gz/.jsonl.gz extensions
with application/gzip content type. New integrations default to
compressed; existing integrations are backfilled as uncompressed.
Closes LFE-8944
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add compressed: false to existing tests and fix form defaults
Existing worker tests download files as plaintext, so they need
compressed: false since the DB default is now true for new rows.
Also add compressed to the UI form defaultValues and reset call.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: drop default model edit button
Button is redundant as it's duplicating the default behavior of the page
* improvement: make warning for default model more self explanatory
- include link to docs
- explain why default model is needed
* chore: extract shared helpers and decompose executeQuery
- Extract sendClickhouseQuery, setSpanQueryAttributes, recordSummaryOnSpan,
and ClickhouseQueryOpts type from duplicated inline code in queryClickhouse
and queryClickhouseStream
- Prevent double ClickHouseResourceError wrapping in queryClickhouseStream
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(dashboards): add SSE streaming endpoint for ClickHouse query progress
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract sendClickhouseQuery, setSpanQueryAttributes, recordSummaryOnSpan,
and ClickhouseQueryOpts type from duplicated inline code in queryClickhouse
and queryClickhouseStream
- Prevent double ClickHouseResourceError wrapping in queryClickhouseStream
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
chore: upgrade release-it to 19.2.4 to resolve undici 6.21.3 vulnerability
Upgrades release-it from ^19.0.4 to ^19.2.4, which ships with undici@6.23.0
instead of 6.21.3, removing the vulnerable transitive dependency.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(deps): upgrade @slack/web-api and @google-cloud/storage to resolve security vulnerabilities
- @slack/web-api ^7.10.0 → ^7.15.0: v7.15.0 requires axios@^1.13.5, fixing HIGH CVE for axios DoS via __proto__ key (Dependabot #163)
- @google-cloud/storage ^7.18.0 → ^7.19.0: v7.19.0 moved to fast-xml-parser@^5.3.4, fixing MEDIUM CVE for entity expansion bypass (Dependabot #225)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
chore(deps): upgrade prettier to 3.8.1, bump typescript-eslint, clean up devDependencies
- Prettier 3.6.2 → 3.8.1 across all packages
- typescript-eslint 8.50.1 → 8.57.1 in packages/config-eslint
- Remove unused @eslint/compat and @eslint/eslintrc from packages/config-eslint
- Remove redundant devDependencies from worker, shared, ee, and web
(eslint-config-standard, eslint-config-prettier, eslint-plugin-prettier,
@typescript-eslint/parser, @typescript-eslint/eslint-plugin — all already
provided transitively via @repo/eslint-config)
- Apply Prettier 3.8 formatting fixes across ~20 files
Note: ESLint 10 upgrade was blocked by eslint-plugin-react incompatibility
(used by eslint-config-next). Will revisit once the React ESLint ecosystem
catches up.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
chore: bump undici and @modelcontextprotocol/sdk to fix security vulnerabilities
Bump undici ^7.18.0 → ^7.24.0 and @modelcontextprotocol/sdk 1.26.0 → 1.27.1
to resolve 9 Dependabot alerts (CVEs in undici, express-rate-limit,
@hono/node-server, hono, and flatted).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: upgrade vulnerable dependencies (dompurify, fast-xml-parser)
- dompurify: 3.2.4 → 3.3.3 (fixes CVE-2025-15599 XSS)
- @google-cloud/storage: 7.18.0 → 7.19.0 (moves to fast-xml-parser ^5.3.4)
- @azure/storage-blob: 12.26.0 → 12.31.0 (moves to fast-xml-parser ^5 via @azure/core-xml 1.5.0)
- @types/nodemailer: 7.0.4 → 7.0.11 (drops @aws-sdk/client-sesv2 dep with old fast-xml-parser)
All fast-xml-parser versions now ≥5.5.6 (fixes CVE-2026-26278)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: revert @azure/storage-blob upgrade to fix Azurite compatibility
@azure/storage-blob 12.31.0 sends x-ms-version 2026-02-06 which is
not yet supported by the Azurite emulator used in CI, causing OTEL
ingestion tests to fail with 500 errors.
Reverting to ^12.26.0 (resolves to 12.26.0 in lockfile). The
fast-xml-parser vulnerability is already resolved since pnpm resolves
@azure/core-xml to 1.5.0 which uses fast-xml-parser ^5.0.7.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* chore: upgrade @langchain/aws to ^1.3.3 to resolve fast-xml-parser vulnerability
The previous @langchain/aws@1.2.x pulled in @aws-sdk/client-bedrock-agent-runtime@3.825.0
which depended on fast-xml-parser@4.4.1 (vulnerable). The new version uses @aws-sdk/*@^3.1006.0
which depends on fast-xml-parser v5.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve TS2349 union type error in withStructuredOutput call
After upgrading @aws-sdk packages, the ChatBedrockConverse type's
withStructuredOutput signature diverged enough from the other chat model
types that TypeScript could no longer call it on the union. Cast to
ChatOpenAI (which has a compatible signature) to fix the build.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: upgrade @langchain/core to 1.1.34 for missing exports
@langchain/aws@1.3.3 requires @langchain/core exports for
'./utils/standard_schema' and './language_models/structured_output'
that were not available in @langchain/core@1.1.18.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: replace Kysely with Prisma for all database queries
Remove Kysely as a dependency and migrate all query builder usage to
Prisma ORM, simplifying the database layer to a single query interface.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove unused DatasetItem import after Kysely removal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct Prisma model names and snake_case column lookups
- Use prisma.datasetRuns (plural) matching the DatasetRuns model name
- Add snakeToCamel fallback in parseDatabaseRowToString for column IDs
like expected_output that map to Prisma's camelCase expectedOutput
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for allDatasetsMetrics tRPC procedure
Cover the $queryRaw SQL that replaced the Kysely compile-to-SQL
pattern in the dataset router, verifying correct JOIN, COUNT, and
GROUP BY behavior for datasets with/without runs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tests): use camelCase field names for Prisma results in filtering tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add versioned dataset item and jsonSelector variable extraction tests
Adds coverage for two previously untested code paths:
1. Versioned dataset items with datasetItemValidFrom - tests exact version match vs latest (validTo=null) fallback
2. jsonSelector via JSONPath for dataset items and traces - tests nested field extraction from JSON columns
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* perf: select only the needed column when fetching dataset items for eval
Instead of fetching the entire dataset item row (which can be large due
to input, expectedOutput, and metadata JSON fields), only select the
specific column referenced by the variable mapping.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tests): update evalService.test.ts for outputDefinition rename and remove kyselyPrisma
- Rename 7 remaining `outputSchema` references to `outputDefinition` (field
renamed in #12540 but tests were incompletely migrated)
- Replace `kyselyPrisma.$kysely` call with `prisma.llmApiKeys.create()`
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(billing): auto-create default spend alerts on new subscriptions
When a new paid subscription is created via Stripe webhook, automatically
create default spend alerts to prevent billing surprises. Thresholds are
plan-specific: Core $200, Pro/Team $1,000, Enterprise $2,000. Skips
creation if the org already has alerts (idempotent). Wrapped in try/catch
so failures don't break the main subscription flow.
Ref: LFE-8154
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: patch review
* chore: tests
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [ChatMLAdapter] Add missing test file
* [ChatMLAdapter] Make the obs download script sort-stable
This allow to re-run the download script without changing the previously downloaded traces/obs ordering, reducing commit noise
* [ChatMLAdapter] Improve "update" mode for the chatml integration test
It will fully create the missing chatml expectation file if needed, simplifying adding new traces
* [ChatMLAdapter] Grand-father the buggy pydantic+gemini trace #11307
* [ChatMLAdapter] Make the obs skip download for preexisting files
* [ChatMLAdapter] Grand-father the buggy csharp agent+gemini trace #12550
* [ChatMLAdapter] Fix gemini taking precedence over pydantic/agent-framework
* [ChatMLAdapter] Remove langchain-deepagent trace for the moment
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
fix(otel): map tool definitions into input payload
Map OTel tool definition attributes into input.tools when gen_ai.input.messages is present so backend tool extraction can persist definitions consistently.
Adds a regression test to prevent future ingestion regressions for gen_ai.tool.definitions mapping.
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* pnpm format
* [ChatMLAdapter] Add test cases against seed spans
Changing the current adaption logic is brittle as several adapters relies on each others (cf the various "exclusions" cases).
Having stronger E2E test for the adapter would make future changes safer.
* [ChatMLAdapter] Clarify test name
* [ChatMLAdapter] Add test case for koog seed
* [ChatMLAdapter] Create dedicated trace folder for tests
Download example traces from the doc + keeps a few traces from the seed file because they are not public.
Old traces (from 2024 for example) are not kept as they corresponing to the v2 SDK
* [ChatMLAdapter] Add exclusions for non-passing observation to make tests pass
This is the starting point.
* [ChatMLAdapter] Create adaption e2e test
Asserting the actual observations --> chatML conversion result this like a better way to improve the conversion logic without being constrained by the current implementation details
* [ChatMLAdapter] Fixing pydantic tool mapping
Example of how the E2E test allows to more finely understand the impact of chaning the mapping logic
* run formatter
* [ChatMLAdapter] Disable spellchecking for traces/chatml test files
* fix spelling
* remove langchain deep
* rename fixture
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
fix(billing): skip payment method check for invoice/wire-transfer customers
For customers paying via invoice/wire transfer (collection_method === "send_invoice"),
the billing page incorrectly showed a "You do not have a valid payment method" error.
This happened because listPaymentMethods() only returns card-type methods, so invoice
customers always had zero results. Now we check the subscription's collection_method
first and only require a payment method for auto-charge subscriptions.
Closes LFE-8872
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(api-auth): parse Basic auth header on first colon only
Replaces split(":") with indexOf + slice so that API secrets
containing colons are preserved rather than silently truncated.
Also adds an early rejection guard when no colon is present.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(api-auth): add header parsing tests for colon-in-secret fix
Covers two cases:
- A decoded Basic auth value with no colon is rejected early with
"Invalid authorization header"
- A decoded value whose password contains colons is parsed correctly
(failure comes from DB lookup, not from credential extraction)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(api-auth): add green test for standard key parsing
Ensures that the colon-split change does not regress authentication
for normal API keys (no colons in the secret).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(api-auth): revert indexOf split; assert key format never contains colon
Reverts the indexOf-based Basic-Auth split back to the original split(':'),
which already rejects malformed headers via the existing !username || !password
guard.
Replaces the three speculative parsing tests with a single, targeted assertion
on the real generated fixture keys: publicKey and secretKey must never contain
a colon. Because keys are generated as `pk-lf-<uuid>` / `sk-lf-<uuid>` (UUIDs
are hex + hyphens only), this is already structurally guaranteed — the test
makes that invariant explicit so any future key-format change that would break
Basic-Auth parsing is caught immediately.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(api-auth): generate 30 key pairs and assert none contain colons
Export generateKeySet so the test can call it directly in a loop
without needing a DB round-trip. Generates 30 key pairs and asserts
neither pk nor sk contains a colon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(clickhouse): add events_green tables for lightweight queries
Add events_green and events_green_input_output tables to split the events
table into a lightweight version (without input/output) for fast queries
and a separate table for full content retrieval. Includes materialized
views to auto-populate from the events table and backfill queries.
See LFE-5394 for ongoing discussion.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: prep new events_full and events_core
* chore: remove backfill queries
* chore: limit backfill events historic to Jan/Dec period
* chore: make compatible with new events layout
* chore: move to use dual event table. WIP commit
* chore: tune settings for initial run
* fix: trace io correct handling. other clenaup
* fix: fix more FROM events occurances
* fix: explicit events_proto in filter column definitions
* fix: more test fixes
* fix: comments and more events references
* chore: some more comment fixes
* fix: update newly added null handling for parentObservationId
* fix: update newly added null handling for parentObservationId
* chore: drop old events table
* chore: adjust boundaries
* chore: typing
* chore: patch tests
* chore: cleanup
* chore: patch schema
* chore: remove unused metadata column
* chore: cleanup
* chore; revert
* chore: tests
* chore: patch tests
* chore: patch tests
* chore: tests
* chore: patch
* chore: patch
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Valery Meleshkin <valeriy@langfuse.com>
Filter out intermediate Prisma spans (serialize, engine query, connection,
response serialization) to keep only the top-level client operation and
db_query spans, reducing per-call span count from 5-6 to 1-2.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(events): add tests for duplicate metadata key resolution
Adds tests asserting that when metadata_names contains duplicate keys,
the first value should be used consistently for both reads and filters.
Currently, mapFromArrays (read path) returns the last value while
indexOf (filter path) returns the first — exposing the inconsistency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(events): use first-value-wins for duplicate metadata keys in
mapFromArrays
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Valery Meleshkin <valeriy@langfuse.com>
* feat: add gemini-live-2.5-flash-native-audio model pricing
Add pricing configuration for gemini-live-2.5-flash-native-audio model
(Vertex AI only) with support for text, audio, image, and video tokens.
* refactor: simplify gemini-live-2.5-flash-native-audio pricing keys
Remove redundant pricing keys (input, output, etc.) from the model entry.
Keep only modality-specific keys for clarity.
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
fix(api): return 404 instead of 501 for v2 APIs on self-hosted instances
Self-hosted users alerting on 5xx patterns get false positives from
the beta-only v2/observations and v2/metrics endpoints returning 501.
Switch to LangfuseNotFoundError (404) since these endpoints are not
available outside Langfuse Cloud.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(prompts): add 'Select all N / Clear' links and search+create input for custom labels
- Replace individual label toggling with a 'Select all N' text link showing
the count of unselected custom labels, and a 'Clear' link to deselect all.
Both links are disabled when the action would be a no-op.
- Move label input to the top of the Custom labels section. The input doubles
as a live search filter and a create trigger: when the typed value has no
exact match in existing labels, an inline 'Create a new label: {input}'
option appears at the bottom of the filtered list.
- Remove the now-unused AddLabelForm component (toggle + separate form).
- The production label section is unchanged to preserve its destructive-action
confirmation UX.
Closes https://github.com/orgs/langfuse/discussions/12468
* formatting
* fix
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* docs: add replay ingestion events v2 README
Documents the new S3 ingestion event replay flow that replaces direct
Redis/ClickHouse/PostgreSQL access with an admin API endpoint, reducing
on-call requirements to just a CSV, host URL, and admin API key.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: document initial athena setup
* chore: add actual replay script
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Pass orderBy to getTracesTableMetrics so ClickHouse uses LIMIT 1 BY
instead of FINAL for deduplication, matching traces.all behavior.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Steffen Schmitz <steffen@langfuse.com>
* feat(playground): enable fulltext search across message windows
* up libs
* fix dep
* fix lock
* fix dependencies
* refactor search to be usable in prompts too
* fix controller
* feat: add intro dialog for Fast (Preview) toggle
Show an informational dialog the first time a user enables the Fast
(Preview) v4 beta toggle, explaining performance improvements and
key changes to the UI.
* feat: show intro dialog from promo banner, swap image to jpg
Move intro dialog state into useV4Beta hook so both the sidebar toggle
and the promo banner trigger the dialog on first enable. Replace png
with jpg image.
* chore: compress intro dialog image from 508KB to 72KB
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* dont show both attributes.metadata and new toplevel metadata
* fix: use startsWith and add ai.telemetry.metadata to metadata dedup filter
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Steffen Schmitz <steffen@langfuse.com>
* fix: update match patterns for newer Claude models
* fix: update match pattern for claude-opus-4-6 to include versioning
* fix line end
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
The customLoader for Next.js Image always prepends a `?` when appending
width and quality parameters. For S3/MinIO presigned URLs that already
contain query parameters, this creates a malformed URL with two `?`
characters, causing signature verification to fail and images to not
render inline.
Use `&` as separator when the URL already contains query parameters.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Nimar <l.nimar.b@gmail.com>
feat(clickhouse): add analytics_events_core view for project-level analytics (LFE-8734)
Adds a ClickHouse VIEW on events_core with per-project, per-hour aggregations
including type/source/scope/SDK counts via sumMap, unique counts via uniqIf
and uniqArray, and has_* boolean flags for feature detection.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(storage): add buffered stream uploader with per-part retry for resilient S3 uploads
* fix(storage): add concurrent part uploads to buffered stream uploader
* chore: put new codepath behind an env var
* chore: make first error handling foolproof
* chore: addressing PR feedback
Batch export streams encoded categorical scores as concat(name, ':', string_value)
in ClickHouse and decoded with split(":") in TypeScript. When a score name contains
colons (e.g. "Name: Subname"), the split incorrectly parses the name/value
pair, causing the value to be dropped and exported as null.
Capture sidebar:v4_beta_toggled event with { enabled } property when users click the v4 Beta toggle, to understand adoption patterns.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Mixpanel's /import?strict=1 API rejects events whose distinct_id matches
a blocklist of "bad IDs" (e.g. "undefined", "null", "0"). This caused
400 errors that threw even when 999/1000 records imported successfully.
- Add MIXPANEL_BAD_DISTINCT_IDS blocklist and isBadDistinctId helper to
transformers; fall back to $insert_id for blocked values
- Parse 400 response JSON in sendBatch; log warning on partial success
instead of throwing
- Add tests covering bad distinct_id fallback for all four transformers
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Use array index as React key instead of the unit name so React doesn't
unmount/remount the input on every keystroke. Fixes LFE-8629.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* perf(dashboards): skip rootEventCondition subquery for wide time windows
For large time windows (>7 days by default), the rootEventCondition
subquery has diminishing returns and causes significant performance
overhead. This makes the filter conditional on the query time window
size, controlled by LANGFUSE_ROOT_EVENT_CONDITION_MIN_HOURS env var
(default: 168h / 7 days). Set to 0 to always apply the filter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: add test case
* chore: adjust test case
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
LEFT JOIN was unnecessary since joined relations always have timestamp
filters in the global WHERE clause that reject NULLs. INNER JOIN lets
ClickHouse optimize join strategy from the start. Also adds a per-relation
useFinal flag (defaults to true) so already-deduplicated tables like
events_core can skip the FINAL modifier.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(dashboard): add filterSql for correct Trace Name filtering on events_traces view
The events_traces view reconstructs trace names via aggregation
(argMaxIf), but filters on "Trace Name" were hitting the endsWith("Name")
fallback and generating `events_core.name IN (..)` — matching observation
names instead of trace names.
Introduces filterSql on view dimensions to support two-phase filtering:
- WHERE pruning: OR'd filters across raw columns for pre-aggregation row reduction
- HAVING: exact match on the aggregated expression after GROUP BY
* chore: switch to theoretically slightly less correct having-less approach. we don't expect traceName to diverge across trace
* chore: cleanup
Define ObservationV2 type in Fern with core fields required and
field-group fields optional, so SDK users get autocomplete and type
safety instead of Record<string, unknown>.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Allow overriding the OAuth token endpoint auth method (e.g.
client_secret_post) per SSO config stored in the database, matching the
capability already available for static env-var-based providers.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
fix(docker): add named volume for Redis to prevent anonymous volume clutter
The redis:7 image declares VOLUME /data in its Dockerfile, causing Docker
to create anonymous volumes when no explicit mapping is provided. This adds
a named volume consistent with how Postgres, ClickHouse, and MinIO are
already configured.
Closes#12187
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
`createProjectMembershipsOnSignup` was unconditionally creating
`ProjectMembership` records for all plans on signup. Since
`resolveProjectRole` uses explicit project memberships over the org
role, this caused the init user (org-level OWNER) to be downgraded
to VIEWER at the project level — blocking API key management and
other owner actions.
Two fixes:
1. `createProjectMembershipsOnSignup`: Skip creating project
memberships when `rbac-project-roles` entitlement is absent.
Without it, users inherit their org role for all projects.
2. `initialize.ts`: For EE plans where project memberships ARE
created, correct the init user's project membership to OWNER
after the org membership is established (fixing the timing
issue where `createUserEmailPassword` runs
`createProjectMembershipsOnSignup` before the org role is set).
Closes#11871
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: parse mlflow attributes on otel spans
* add observation types
* ingestion
* trace tree - hide DEBUG but not children
* format
* fix
* add on_enter and start_agent_activity as debug spans
* consolidate tests
* dont map if error
* only map livekit traces
* speed up
* consolidate tests
* fix issue of reverse ordering when root span is hidden
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
The observations_agg CTE only included level-related columns, causing
ClickHouse errors when eval automations filtered on latency, cost, or
token columns (e.g. "Identifier 'o.latency_milliseconds' cannot be
resolved"). Add latency_milliseconds, usage_details, and cost_details
aggregations to the CTE.
* feat(webhooks): add optional user field to webhook and entity change schemas
Add user info (id, name, email) as optional field to
PromptWebhookOutboundSchema, WebhookOutboundEnvelopeSchema, and
EntityChangeEventSchema so triggering user context flows through the
event pipeline to outbound webhook/GitHub dispatch payloads.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(webhooks): thread triggering user info from tRPC call sites through event sourcing
Pass ctx.session.user (id, name, email) from all prompt mutation
call sites (create, duplicate, delete, deleteVersion, setLabels,
setTags) into promptChangeEventSourcing, which forwards it into the
entity change queue payload.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(webhooks): include user info in outbound webhook and GitHub dispatch payloads
Thread user from entity change event through prompt version processor
to webhook queue, then include in final HTTP payload for both webhook
and GitHub dispatch actions. User field is optional and omitted when
not available (e.g. API-key-triggered changes).
Co-Authored-By: Claude <noreply@anthropic.com>
* test: add user info tests for webhook and GitHub dispatch payloads
Adds three tests verifying user info is correctly included in webhook
payloads when provided, omitted when absent, and included in GitHub
dispatch payloads.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(webhooks): remove user id from outbound payloads
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Max Deichmann <m.deichmann@tum.de>
* feat: add "Sign in with ClickHouse Cloud" auth provider (Cloud only)
Adds a dedicated clickhouse-cloud auth provider using Auth0Provider under
the hood with a custom provider ID, giving it its own callback URL
(/api/auth/callback/clickhouse-cloud). Gated behind NEXT_PUBLIC_LANGFUSE_CLOUD_REGION
so it only appears on Langfuse Cloud.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: lint
* chore: use clickhouse icon
* chore: overwrite audience
* chore: change audience to langfuse
* chore: skip audience
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The queryCostByType and queryUsageByType calls in ModelUsageChart were
not passing the version prop, so they always defaulted to v1 and hit
the legacy `observations FINAL` path instead of the faster v2
`events_core` path.
Remove hardcoded "public" schema prefix and add IF EXISTS/IF NOT EXISTS
guards so the migration works with custom Postgres schemas. Add cleanup.sql
entry to force re-application on existing deployments with stale checksum.
Fixes#11946
* chore: send webhooks for admin access
* test: improve admin access webhook test robustness and coverage
Move env restoration and fake timer cleanup into afterEach for proper
test isolation. Add tests for dedupe with different keys, fetch
rejection, and non-ok response handling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: fixes
* fix: fixes
* fix: fixes
* fix: fixes
* fix: enable e2e tests again
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* chore(events-table): use aggregate filter builder for scores
* fix build
* fix
* add clarification comments
* performance with having clause
* refactor(scores): replace eventsTracesAggregation with flat events query for v4 scores
Replace the heavy GROUP BY aggregation builder (eventsTracesAggregation) with a lightweight flat EventsQueryBuilder (eventsTraceMetadata) that selects one row per trace via LIMIT 1 BY.
* clean up
* add warning
* only show trace cols as filter where relevant
---------
Co-authored-by: Valery Meleshkin <valeriy@langfuse.com>
* feat(experiments): add experiments pages with routing and admin flag checks
* chore: only allow experiments fir cloud admins
* chore: lint
* chore(experiments): remove unused projectId variable from experiments and experiment detail pages
* refactor(web): unify server test layout and CI
* refactor(web): remove pruneDatabase CI check and dead helper
* test(web): stabilize queryBuilder and model definitions assertions
* chore: move tests to async
* chore: move tests to async
* chore: move tests to async
* chore: move tests to async
* test(web): make model definitions assertions pagination-safe
* test(web): gate media e2e checks for azure blob mode
* test(web): fix azure media test gating condition
* test(web): make api-auth redis hooks cluster-compatible
* test(web): avoid redis quit errors in cluster hooks
* fix(web): use injected redis client for api key invalidation
* test(web): harden api-auth redis cluster test client lifecycle
* chore: move tests to async
* chore: move tests to async
* chore: move tests to async
fix(useLangfuseEnvCode): remove spurious whitespace
Avoid whitespace around the = operator (eg KEY=VALUE, not KEY = VALUE)
to prevent parsing errors, esp in Docker and standard .env parsers
Co-authored-by: Nimar <l.nimar.b@gmail.com>
Custom dashboard widgets were always defaulting to v1 queries,
missing the uniq(trace_id) optimization enabled by the v4 beta flag.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat(dashboard): optimize v2 traces queries with uniq(trace_id) on
observations view
Replace the slow eventsTracesView path (rootEventCondition subquery +
high-cardinality GROUP BY trace_id) with uniq(trace_id) on the
eventsObservationsView for dashboard trace tiles in v2.
Add info icon to filter sidebar tooltips
Filters with tooltips (e.g. "Is Root Observation") showed tooltip
text on hover but had no visual indicator. Add an InfoIcon next to
the label to signal that additional information is available.
https://claude.ai/code/session_017NTanXuBd3ta65RgAxGn2d
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add observation type icons to filter sidebar options
Show observation type icons (SPAN, GENERATION, EVENT, etc.) next to
filter checkbox labels in the sidebar for better visual indication.
Adds a generic renderIcon prop to CategoricalFacet config, threaded
through the filter state and UI components, and enables it for the
observation type filter in both observations and events tables.
https://claude.ai/code/session_01LC2guy6qBCEGzoGyGiUDvQ
* push
---------
Co-authored-by: Claude <noreply@anthropic.com>
In v2, traces queries on the eventsTracesView are slow due to a trace_id IN
(subquery) double-scan and two-level aggregation. Reformulate them to query
eventsObservationsView with a parentObservationId IS NULL filter instead,
which enables single-level query optimization.
Extends the executeQuery / QueryBuilder system with a pairExpand concept for clause-level ARRAY JOIN on ClickHouse Map columns, enabling the "Cost by type" and "Usage by type" tabs of ModelUsageChart to use the v2 events path.
Add Gemini 3.1 Pro Preview model pricing and LLM type
Add gemini-3.1-pro-preview to default model prices with standard
($2/$12 per MTok) and large context ($4/$18 per MTok) pricing tiers,
matching the official model card. Supports both gemini-3.1-pro-preview
and gemini-3.1-pro-preview-customtools model IDs. Also adds the model
to vertexAIModels and googleAIStudioModels arrays in types.ts.
https://claude.ai/code/session_01Cn1PsAdzvzSRz9r7mgBz4E
Co-authored-by: Claude <noreply@anthropic.com>
Wire the metricsVersion prop through to the chart tRPC endpoint so
the ScoresTable widget queries events_core (v2) instead of traces (v1)
when the dashboard beta toggle is enabled.
* feat(worker): add SYSTEM SYNC REPLICA before INSERT-SELECT in event propagation
Execute SYSTEM SYNC REPLICA observations_batch_staging LIGHTWEIGHT before the
INSERT-SELECT to ensure the replica has all parts, avoiding stale reads due to
replication lag. Both commands share a session_id for sticky routing to the same
ClickHouse node, as recommended by the ClickHouse support team.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: 5min timeout
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Track the time between partition timestamps and current time to monitor
event propagation lag. Two new gauge metrics are published:
- last_processed_partition_delay_seconds: recorded on every job run based
on the Redis cursor, providing a reference even when no processing
happens or processing fails
- processed_partition_delay_seconds: recorded after successfully
propagating a partition
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* chore(preview-data): adjust usePreviewData to read from event batch IO for v4
* fix(observations-table): add session ID column to observations table mapping for filtering
* chore: types
* chore: lint
* feat(events-table): add EventsSessionAggregationQueryBuilder for
single-step session aggregation
Replace the two-step trace→session aggregation in sessions table/metrics
queries
with a direct GROUP BY session_id approach on the events_core table.
This avoids
redundant intermediate aggregation and pushes session ID filters into
the inner CTE
for better performance. Also rewrites session tests to exercise both
legacy and
events code paths based on LANGFUSE_ENABLE_EVENTS_TABLE_V2_APIS.
* chore: more builder-heavy code reorganization
* chore: fix CI
Add bloom_filter(0.01) indexes on user_id and session_id to events_core
and events_full tables. Remove idx_type set index as type is already
LowCardinality and rarely filtered without start_time.
Migration commands to run on ClickHouse Cloud:
```sql
-- events_core
ALTER TABLE events_core ADD INDEX IF NOT EXISTS idx_user_id user_id TYPE bloom_filter(0.01) GRANULARITY 1;
ALTER TABLE events_core ADD INDEX IF NOT EXISTS idx_session_id session_id TYPE bloom_filter(0.01) GRANULARITY 1;
ALTER TABLE events_core DROP INDEX IF EXISTS idx_type;
ALTER TABLE events_core MATERIALIZE INDEX IF EXISTS idx_user_id;
ALTER TABLE events_core MATERIALIZE INDEX IF EXISTS idx_session_id;
-- events_full
ALTER TABLE events_full ADD INDEX IF NOT EXISTS idx_user_id user_id TYPE bloom_filter(0.01) GRANULARITY 1;
ALTER TABLE events_full ADD INDEX IF NOT EXISTS idx_session_id session_id TYPE bloom_filter(0.01) GRANULARITY 1;
ALTER TABLE events_full DROP INDEX IF EXISTS idx_type;
ALTER TABLE events_full MATERIALIZE INDEX IF EXISTS idx_user_id;
ALTER TABLE events_full MATERIALIZE INDEX IF EXISTS idx_session_id;
```
Monitor materialization progress:
```sql
SELECT * FROM system.mutations WHERE table IN ('events_core', 'events_full') AND is_done = 0;
```
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add REDIS_CLUSTER_SLOTS_REFRESH_TIMEOUT env variable to allow configuring
the ioredis slotsRefreshTimeout for Redis cluster connections. Defaults
to 5000ms when not set.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
MOVED responses are normal Redis Cluster behavior where the server
tells the client a key lives on a different node. ioredis Cluster
handles these automatically. Logging them at warn created noise and
confused self-hosted customers into thinking they had connection issues.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(observations-v2): retire parseIoAsJson option, return 400 when set to true
* fix(test): align parseIoAsJson test assertion with middleware error format
The withMiddlewares error handler puts Zod validation details in the
`error` field (issues array), not the top-level `message`. Update the
test to check the correct field after merging from main.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(clickhouse): add events_green tables for lightweight queries
Add events_green and events_green_input_output tables to split the events
table into a lightweight version (without input/output) for fast queries
and a separate table for full content retrieval. Includes materialized
views to auto-populate from the events table and backfill queries.
See LFE-5394 for ongoing discussion.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: prep new events_full and events_core
* chore: remove backfill queries
* chore: limit backfill events historic to Jan/Dec period
* chore: make compatible with new events layout
* chore: move to use dual event table. WIP commit
* chore: tune settings for initial run
* fix: trace io correct handling. other clenaup
* fix: fix more FROM events occurances
* fix: explicit events_proto in filter column definitions
* fix: more test fixes
* fix: comments and more events references
* chore: some more comment fixes
* fix: update newly added null handling for parentObservationId
* fix: update newly added null handling for parentObservationId
* chore: undo the write path changes. prepare for hybrid deployment.
* fix: allow legacy events read on the backfill path
* perf: ensure toStartOfMinute is present in queries ordering by time
* perf: ensure toStartOfMinute is present in queries ordering by time
* perf: remove project_id from ordering when not needed
* perf: don't truncate IO and use CTE-based split query in v2 observations API
* fix: post merge fixes
* chore: cleanup IO read optmimization implementation.
* enable prod-hipaa deplo
* chore: patch naming
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Valery Meleshkin <valeriy@langfuse.com>
feat(api): add self-hoster controls for GET /api/public/traces
Add three environment variables to give self-hosters control over the
GET /api/public/traces endpoint performance:
- LANGFUSE_API_TRACES_REJECT_NO_DATE_RANGE: reject requests without fromTimestamp (400)
- LANGFUSE_API_TRACES_DEFAULT_DATE_RANGE_DAYS: auto-apply a default date range
- LANGFUSE_API_TRACES_DEFAULT_FIELDS: restrict default field groups (e.g. "core")
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: head based opt-in for the direct writes into v4 tables
* feat: enable direct event writes for all OTEL spans via HTTP with underscore header support
* feat(mixpanel): add project name to Mixpanel integration events
Add langfuse_project_name property to all events sent to Mixpanel integration
alongside the existing langfuse_project_id. This allows users to filter and
analyze Mixpanel data using human-readable project names instead of opaque UUIDs.
Changes:
- Fetch project name from PostgreSQL in job handler
- Pass project name through all repository functions
- Add langfuse_project_name to all 4 event types (traces, generations, scores, events)
- Update tests with new field and add specific test case for project name
Fixes: https://github.com/langfuse/langfuse/issues/12037
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
* fix(posthog): add project name to PostHog integration events
- Add projectName to PostHogExecutionConfig type
- Fetch project name from PostgreSQL in handlePostHogIntegrationProjectJob
- Pass project name as parameter to analytics integration functions
- Mirrors the changes made for Mixpanel integration
* ci: rerun CI tests
---------
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Co-authored-by: Nimar <l.nimar.b@gmail.com>
fix: add mutual exclusion between temperature and top_p for Anthropic models
Fixes#11965
## Problem
The Playground allows enabling both `temperature` and `top_p` simultaneously for Anthropic models (e.g., `claude-sonnet-4-5-20250929`). However, Anthropic's API does not allow both parameters to be specified together, which results in a 400 error:
```
'temperature' and 'top_p' cannot both be specified for this model.
Please use only one.
```
## Solution
Added automatic mutual exclusion logic in `useModelParams` hook:
- When enabling `temperature` for Anthropic models, automatically disable `top_p` if it's enabled
- When enabling `top_p` for Anthropic models, automatically disable `temperature` if it's enabled
## Changes
- Modified `web/src/features/playground/page/hooks/useModelParams.ts`
- Updated `setModelParamEnabled` to handle mutual exclusion for Anthropic models
- Only applies when enabling a parameter (disabling both is still allowed)
- Only affects Anthropic adapter
## Testing
Manual testing:
1. Open Playground
2. Select Anthropic model (e.g., `claude-sonnet-4-5-20250929`)
3. Enable temperature → top_p is automatically disabled
4. Enable top_p → temperature is automatically disabled
5. Other providers (OpenAI, etc.) are unaffected
## Risk
**Low** - Change is scoped to Anthropic models only and prevents invalid API calls.
Other providers are completely unaffected.
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* fix(charts): disable cursor and introduce bar hover state
* fix(charts): make dark mode bar chart hover light instead of dark
* fix(charts): dynamic right margin for value labels
* remove comment
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* feat: Delete Functionality for Folders added with confirmation dialog
* fix: updated test cases for delete folder to be more comprehensive for nested folders
* fix: renamed all items, contents occurences to prompts
* fixed linter warning
* fix: properly formatted delete-folder.tsx with prettier
* add prompt dependency
* delete related prompts
* style
* fix: escape LIKE injection
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* feat(evals): add default type=GENERATION filter for observation evaluators
Prevents users from accidentally running evaluators on every observation
type when creating a new live observations evaluator. Also fixes an
inconsistency where the filter default fallback used TRACE while the
target default was EVENT, and applies appropriate default filters when
switching between targets.
https://claude.ai/code/session_01GC73fWEut8U1LZjyvzpLs7
* feat(evals): add inline warning when no filters are set on evaluator
Shows a non-dismissible alert below the filter section warning that the
evaluator will run on all observations/traces/experiments when no
filters are configured, prompting users to verify this is intended.
https://claude.ai/code/session_01GC73fWEut8U1LZjyvzpLs7
* push
* push
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: limit height for eval template text area (#11993)
feat: height limited inputs for eval template
* chore: remove minHeight="none" from various components for improved flexibility
---------
Co-authored-by: Dustin Healy <54083382+dustinhealy@users.noreply.github.com>
The hourly-key approach from #11988 broke jobId deduplication, causing an
ever-growing queue where each scheduler cycle added new jobs regardless of
whether previous ones had completed. Revert to static jobId (projectId +
lastSyncAt) for proper deduplication and use removeOnFail: true so failed
jobs are immediately cleaned from Redis and don't block re-queuing.
Includes a one-time migration that drains legacy hourly-key jobs on first
scheduler run after deploy.
Add copy to the SDK/API card description explaining that users can
configure runs via webhook using the button below.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add an hourly key to the BullMQ processing jobId so that failed jobs
from a previous hour don't permanently block re-queuing of the same
project. Previously, when lastSyncAt was NULL the jobId was static,
causing a single failure to deadlock the integration forever.
Also add stalling protection (lockDuration/stalledInterval/maxStalledCount)
to the Mixpanel processing worker, [MIXPANEL]/[POSTHOG] log
prefixes, try/catch around both processing pipelines, and extract cron
patterns into named constants.
* fix(dashboard): prevent mutation of predefined colors in getColorsForCategories
* feat(schema): add AREA_TIME_SERIES to dashboard widget chart types
* feat(widgets): add area time series chart, optional rowLimit, and Chart props
* fix(widgets): handle AREA_TIME_SERIES in DashboardWidget rowLimit
* style(ui): replace Tremor utility classes with Tailwind equivalents
* refactor(ui): replace Tremor Card and Divider in integrations and playground
* feat(dashboard): beta toggle and Recharts in legacy dashboard cards
* fix(ee): replace Tremor in BillingUsageChart
* feat(charts): improve horizontal bar chart spacing and layout
* feat(charts): time series legend above chart, scrollable and click-to-highlight
* feat(charts): time series legend right-align, solid grid, legacy-style lines
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(charts): tooltip legacy-style layout, right-align values, compact axis and $ for cost
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(charts): use inline chart color template literal instead of CHART_COLORS array
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(dashboard): user chart expand with bars, shared bar chart height constants
- TabsComponent: remove h-3/4 so tab content sizes to content, chart no longer compressed
- UserChart: remove flex-1 from chart wrapper so height is bar-count based
- UserChart: use same BAR_ROW_HEIGHT/CHART_AXIS_PADDING height math as TracesBarListChart
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(dashboard): fixed-height wrappers for Recharts in tabs and score analytics
- TracesTimeSeriesChart, ModelUsageChart, LatencyChart: h-80 shrink-0 so
legend + chart get space in tab content; wrap legacy Traces chart in same
- NumericScoreTimeSeriesChart, NumericScoreHistogram, CategoricalScoreChart:
h-80 shrink-0 so beta charts render in Scores Analytics grid
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(charts): add configurable bar chart value labels
* feat(charts): use consistent tooltips across all recharts
* fix: scrollable legend
* refactor(charts): move dataset run charts to recharts
* chore(charts): gate toggle behind langfuse email
* feat(charts): align color palettes
* fix: don't show data point dots and don't force load chart
* fic: revert unintended
* update
* feat(charts): add subtle_fill option for recharts
* formatting
* feat(charts): remove time from charts if aggregation is by day
* chore: address PR feedback
* chore: remove dot indicators for all home dashboards
* move migration
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* Initial plan
* perf: optimize hasAnyTrace with conditional polling, max_threads=1, and Redis cache
- Frontend: stop refetchInterval once hasTracingConfigured is true
- Backend: add max_threads=1 to LIMIT 1 existence check (71% row reduction)
- Backend: cache positive results in Redis with 24h TTL
Co-authored-by: sumerman <222471+sumerman@users.noreply.github.com>
* fix: revert frontend refetchInterval changes that caused CI build failure
Co-authored-by: sumerman <222471+sumerman@users.noreply.github.com>
* perf: replace Redis cache with PostgreSQL hasTraces flag and propagate to frontend
- Add `has_traces` boolean column to Project model (default false, never reverted)
- hasAnyTrace checks PG flag first, skips ClickHouse if already set
- Persist positive result to PG with conditional update (only if not already set)
- Propagate hasTraces to frontend session via auth.ts and next-auth.d.ts
- Frontend pages use session flag to skip polling entirely for established projects
- Remove Redis caching from hasAnyTrace (replaced by permanent PG flag)
- Keep max_threads=1 optimization for the ClickHouse existence check
Co-authored-by: sumerman <222471+sumerman@users.noreply.github.com>
* fix: tests and FE fixes
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sumerman <222471+sumerman@users.noreply.github.com>
Co-authored-by: Valery Meleshkin <valeriy@langfuse.com>
* feat(evals): single observation evals for prompt experiments
* push
* push
* push
* push
* push
* chore(evals): fix types
* chore: gate is beta
* feat: add default ACTIVE status filter with user interaction respect
- Add defaultFilters parameter to useSidebarFilterState hook
- Apply default filter to show only ACTIVE evaluators on /evals page
- Track user interaction with useRef to respect manual "clear all" action
- Default filter reapplies on fresh page visits but not after user clears filters
- Filter is visible in UI and can be modified by users
- Clean up unused imports in inner-evaluator-form.tsx
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* chore: build
* chore: lint
* fix: typo in prompt=experiments in seeder and internal environments
* fix: build
---------
Co-authored-by: Hassieb Pakzad <68423100+hassiebp@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* chore: add callout
* chore: callout variants
* feat: add remapping callouts
* fixup: add remapping wizard
* fixup: allow new eval types in eval set up
* fixup: format
* chore: force sequential consistency for dual write (#11764)
* Revert "chore: force sequential consistency for dual write (#11764)"
This reverts commit 6860a0beba.
* chore: bump nextjs from 15.5.9 to 15.5.10 (#11772)
* chore: bump turbo to 2.7.6 (#11775)
* fixup: final mapping logic
* chore: has otel sdk configured trpc route
* fix(ui): standardize callout dismiss button to always use X icon
- Remove conditional "Dismiss" text button
- Always show X icon for dismiss action
- Simplify button styling to consistent h-6 w-6 size
- Action buttons remain positioned to the left of dismiss button
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* refactor(evals): use EvalTargetObject constants and type helpers
- Add comprehensive type helper functions in typeHelpers.ts
- Replace all string literal comparisons with EvalTargetObject constants
- Add helpers: isTraceTarget, isEventTarget, isDatasetTarget, isExperimentTarget, isTraceOrEventTarget
- Update all eval components to use constants instead of hardcoded strings
- Improves type safety and prevents typos in target comparisons
Files updated:
- web/src/features/evals/utils/typeHelpers.ts (added helper functions)
- web/src/features/evals/utils/evaluator-form-utils.ts
- web/src/features/evals/components/eval-version-callout.tsx
- web/src/features/evals/components/legacy-eval-callout.tsx
- web/src/features/evals/components/remap-eval-wizard.tsx
- web/src/features/evals/components/inner-evaluator-form.tsx
- web/src/features/evals/components/evaluator-table.tsx
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* refactor(filters): extract severity styling logic into helper function
- Create getSeverityStyles helper function to map severity levels to CSS classes
- Replace inline ternary chains with clean lookup-based styling
- Reduces complexity in the filter column rendering logic
- Improves readability and maintainability
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* chore: push
* chore: push
* chore: push
* refactor(evals): extract synchronized scroll logic into custom hook
- Create useSynchronizedScroll hook in hooks/useSynchronizedScroll.ts
- Simplifies RemapEvalWizard by removing inline useEffect
- Reusable hook for any dual-panel synchronized scrolling
- Improves code organization and testability
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(evals): make useSynchronizedScroll hook generic for type safety
- Add generic type parameters for left and right element types
- Allows hook to work with specific HTML element types (HTMLDivElement, etc.)
- Fixes TypeScript error when passing RefObject<HTMLDivElement>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* chore: push
* docs: adjust wording
* fix(ui): update disabled state styling for input, select, and textarea components
- Adjusted styles to include a muted background for disabled states in Input, Select, and Textarea components.
- Ensures better visual feedback for users interacting with disabled form elements.
* docs: wording
* chore: fix eslint
* Revert "chore: has otel sdk configured trpc route"
This reverts commit 23e65e7115e0d67915d826eb86e3d7333dc115c3.
* chore: mock if otel data or not
* chore: fix links
* fix: do not support new evaluators in prompt experiments yet
* chore: lint
* fix: add filters values for event/experiment evals
* chore: lint
* fix: add trace_name filter options
* chore: persist col.id in filter builder
* chore: Extend ObservationsTable
* chore: streamline observation evaluation filters and enhance ObservationsTable integration
* chore: push
* chore: refactor observation evaluation functions and improve filter column mapping
* chore: reorganize evaluator form utilities and constants for improved clarity and functionality
* chore: implement useEvalConfigFilterOptions hook for centralized filter management in evaluator form
* chore: enhance evaluation prompt preview and variable mapping functionality with new hooks and components
* chore: lint
* fixup: url management and detail navigation
* feat: implement URL query parameter management for target changes in useEvalConfigMappingData hook
* chore: fix detail navigation
* chore: move evaluator remapping to separate page
* chore: hide eval experience behind feature switch
* chore: fix lint
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* chore: fix
* chore: fix test
* chore: fix test
* chore: fix test
* chore: adjust typing
* chore: types
* chore: types
* chore: fix test
* chore: tests
* chore: lint
* chore: test
* chore: test
* chore: test
* chore: fix redirect
* fix: integrate observation evaluations into variable extraction logic
* fix: add name filter options to observation evaluations
* chore: lint
* feat: add default filter for ACTIVE status on evaluators table
- Add defaultFilters parameter to useSidebarFilterState hook
- Apply default filter to show only ACTIVE evaluators on /evals page
- Default filter is applied once per project and stored in localStorage
- Filter is visible in UI and can be modified by users
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Revert "feat: add default filter for ACTIVE status on evaluators table"
This reverts commit c0b10fe42ca58b4696881e0e4e9bb2c999cc6608.
---------
Co-authored-by: Steffen Schmitz <steffen@langfuse.com>
Co-authored-by: Nimar <l.nimar.b@gmail.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: add server-side ingestion masking for OTEL traces
Add an enterprise feature that allows masking/redacting sensitive data
from OTEL traces before storage. Users can configure an external HTTP
callback endpoint that receives trace data and returns masked versions.
- Add ingestion masking module with configurable callback URL, timeout,
retry logic, and fail-open/fail-closed modes
- Add reusable isEnterpriseLicenseAvailable utility in licenseCheck
- Integrate masking into OTEL ingestion queue processing
- Add environment variables for configuration
- Add unit tests for masking functionality
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: patch tests
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix(public-api): add trace_id to observations query filter
Include trace_id in the clickhouse_keys CTE and IN clause filter
to improve query performance by better utilizing ClickHouse indexes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: release v3.150.1-0
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* Add links to docs in tracing filter view when key features are not being used
- done for sessions, tags, environments
* formatting
* Adjusted empty state starting screen trace view
- removed feature boxes below video
- put the get-started steps on the page directly instead of behind a button
- adjusted copy
* Adjusted Sessions empty state screen
- aligned layout with tracing screen
* Adjust Users view empty state
- to align with other empty state views
* Updated copy of sessions empty state screen
* Enable links in info hover popup text
- Integrated ReactMarkdown for rendering descriptions in DocPopup and Popup components, allowing for formatted text and links.
- Added links to relevant info text: tags, metadata, sessions, userId, version, and release
- Updated the info hover text on the titles in the Sessions, Traces, and Users views
* fix linting errors
* addressed comments from depthfirst bot
* fix user ID link
* refactor(doc-popup): replace markdown with JSX for hover links
Remove react-markdown/remark-gfm dependency and use JSX with inline
<a> tags instead. This simplifies the codebase by avoiding the need
for a markdown parser just for rendering links in hover popups.
- Remove MarkdownContent component from doc-popup.tsx
- Update type definitions to accept ReactNode for descriptions
- Convert markdown link syntax to JSX in page headers and table tooltips
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* chore(job-executions): add nullable col "job_input_dataset_item_valid_from"
* feat(dataset-versioning): implement dataset versioning support across APIs and schemas
- Enhanced dataset items and dataset run items APIs to accept a version parameter for retrieving historical data.
- Updated schemas to include optional dataset version fields, allowing for precise dataset item retrieval based on timestamps.
- Added validation for version parameter to ensure datasetName is provided when specified.
- Implemented tests to verify functionality of dataset versioning in API responses and experiment runs.
* fixup: support running versioned experiments in UI
* chore(dataset-versioning): add datasetItemVersion support across schemas and components
* fix(dataset-versioning): update datasetVersion handling in forms and APIs
* chore: fix typing
* fix: test
* chore: fix
* chore: push
* chore: reorder migration
* chore: rebase
* chore: fix
* fix: reinstanting RedisLock and repeatable job. Away with BullMQ.
Revert "feat: move batchProjectCleaner to use BullMQ (#11504)"
This reverts commit 26ae2080d0.
* chore: refactor BatchDataRetentionCleaner and MediaRetentionCleaner to use Periodic Runner
* chore: reel in logging a little
* chore: adjust batch data retention behaviour + lock jitter
* chore: MediaRetentionCleaner should not run when redis is unavailable
* chore: tracing in periodic runners
* perf(ui): reduce initial bundle size with dynamic imports
- extract RootProvider.tsx and AnalyticsProvider.tsx for code splitting.
- Add dynamic imports and in layout.tsx and _app.tsx for heavy
dependencies
- Create MobileDrawer and ResizableDesktopLayout and use dynamic imports
for lazy loading in layout.tsx
* perf(ui): split command-menu into smaller components for optimized rendering, memoize commend menu context and CommandMenu
* perf(ui): improve INP performance of traces table opening closing sidebar with reusable ResizableDesktopLayout
- Extract ResizableDesktopLayout into reusable component with
configurable props
- Update opening closing of support drawer in layout.tsx to use new
ResizableDesktopLayout.
- Update toggling of filters sidebar in traces view to use
ResizableDesktopLayout. Mark it as a transition.
* perf(ui): lazy load posthog and PosthogProvider
* dont lazy load psthog
* clena p
* fix build
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* feat: introduce global v4 beta toggle for all events based views
* fix: add hook and toggle
* move up
* fix: styling and naming
* fix: check correct feature flag
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
fix: update LibreChat reference to correct repository
Update LibreChat references in all README files (en, cn, ja, kr) to point to the correct repository (danny-avila/LibreChat) with accurate star count (33,142) and proper sorting position.
Co-authored-by: Nimar <l.nimar.b@gmail.com>
When a filter column doesn't match any UI/CH table mapping, the error log
now includes the invalid column name, filter type, and all available columns
for the table. This helps diagnose issues where users accidentally send
filters for one table to a different table's endpoint.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix(e2e-tests): change wait pattern not to be fixed
* fix timeouts
* redirect after sign in
* wait for button to be enabled before click
* insane timeouts
* show debug
* remove clutter
* clean
* run test one after another
* wait for sign up
* fix double config
* disable E2E tests
* feat(auth): support multiple default orgs/projects for automated access provisioning
Extend LANGFUSE_DEFAULT_ORG_ID and LANGFUSE_DEFAULT_PROJECT_ID to accept
comma-separated lists of IDs, enabling automatic provisioning of new users
to multiple organizations and projects on signup.
- Update env.mjs Zod schemas to parse CSV strings into arrays
- Refactor createProjectMembershipsOnSignup to iterate over arrays
- Maintain backward compatibility with single-value configs
- Add documentation comment to .env.prod.example
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: update example
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: move early return so that pending_projects is always updates
* chore: add an oldest work item age metric
* chore: time past cutoff instead of just age
* chore: bump retention delete timeout
* fix: use the corret lower bound for reduce
* feat: an alternative to retention queue: batch-oriented periodic jobs.
* Update worker/src/features/batch-data-retention-cleaner/index.ts
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
* chore: validate that we only operate on supported tables
* fixing a similar potential security issue for BATCH_DELETION_TABLES
---------
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
* fix(scores-table-cell): add copy to clipboard functionality and fix scroll-behaviour
* fix(scores-table-cell): prevent event propagation in copy to clipboard handler
Apply the same LANGFUSE_S3_LIST_MAX_KEYS limit (default: 200) to Google
Cloud Storage and Azure Blob Storage listFiles methods for consistency
with S3 implementation. This prevents potential resource exhaustion when
listing large numbers of files.
Closes#11394
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Replace per-record "Max attempts reached, dropping record" log messages
with a single summary log showing the total count of dropped records.
This reduces log noise while maintaining the same error metric for alerting.
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add organization-level audit logs viewing
Organization-level changes (org CRUD, project CRUD, membership changes)
were being logged to the database but had no UI or API to view them.
This change adds:
- `auditLogs:read` scope to organization access rights (OWNER, ADMIN)
- `allByOrg` tRPC endpoint in auditLogsRouter for org-level audit logs
- OrgAuditLogsTable component for displaying org-level audit logs
- OrgAuditLogsSettingsPage component with entitlement/access checks
- "Audit Logs" tab in organization settings (visible with audit-logs entitlement)
Organization audit logs show changes where projectId is null, including:
organization create/update/delete, project create/delete/transfer, and
organization membership changes.
* refactor(audit-logs): unify project and org audit log tables
Consolidate OrgAuditLogsTable into AuditLogsTable using discriminated
union props to support both project and organization scopes. This
removes ~130 lines of duplicate code while preserving all functionality.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Steffen Schmitz <steffen@langfuse.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat: cursor-based sequential processing for event propagation
Replace lock-based parallel partition processing with cursor-based
sequential processing for the event propagation job:
- Track last processed partition in Redis cursor
- Process partitions sequentially in chronological order
- Rely on ClickHouse table TTL (12h) for partition cleanup instead of
explicit DROP PARTITION calls
- Enforce global concurrency of 1 in queue configuration
- Remove lock functions and multi-job scheduling
This improves debuggability by keeping data in observations_batch_staging
for 12 hours, allowing verification of the data pipeline when issues occur.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: increase buffer before partition processing
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat(corrections): add corrected vs actual output diff
* chore(corrections): enhance editing state management and auto-save functionality in CorrectedOutputField
Add source type (EVAL, ANNOTATION, API) to score names in distribution
chart legends to distinguish scores with identical names. This matches
the existing behavior in timeline charts and prevents confusion when
comparing e.g. 'friendliness (EVAL)' vs 'friendliness (ANNOTATION)'.
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* chore(dataset-items): drop sys_id col
* chore(dataset-item-events): drop foreign key constraint from dataset_item_events
* chore(dataset-item-events): remove DatasetItemEvent model and associated migration
* chore: reorder migrations
* feat: add BatchProjectCleaner as an optimization when multiple project
deletions are pending.
* chore: add PeriodicRunner abstract base class for periodic task
execution
* chore: adjusting MutationMonitor for project deletion.
* chore: extracting RedisLock utility; lock ownership fix.
* fix(api): make retention optional and fix metadata handling in update project
- Make retention field optional in update project API to retain existing
setting when omitted
- Fix metadata spreading to only apply when defined, preventing null
overwrites
- Update Fern API spec and OpenAPI documentation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* Update web/src/ee/features/admin-api/server/projects/projectById/index.ts
Co-authored-by: depthfirst-app[bot] <184448029+depthfirst-app[bot]@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: depthfirst-app[bot] <184448029+depthfirst-app[bot]@users.noreply.github.com>
* chore: double-check that a project still exists before starting potentially expensive DELETE
* chore: appling the same pre-flight SELECT to data retention queries
* fix: use feature flag for event-based test
* fix: CI for worker should be able to test event tables
* fix: update match patterns to include global region for Claude models
* fix: add missing newline at end of default-model-prices.json
* fix: removed global prefix from regex for models without global inference support
* fix: add missing newline at end of default-model-prices.json
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
Refactored the CSV field escaping logic into a reusable `escapeCsvField`
utility function that properly escapes double quotes and wraps fields.
Applied this function to both headers and body rows, fixing an issue
where headers containing commas would break CSV parsing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat(api): allow hight cardinality measures in v2/metrics when its topN
* chore: getting rid of preflight in favor of using
max_bytes_before_external_group_by
* fix(api-docs): sync Fern API types with TypeScript definitions
- Update fern/apis/server/definition/commons.yml to match TypeScript types
- Add source file references to each Fern type definition
- Fix nullable vs optional type mappings:
- .nullable() → nullable<T>
- .nullish() → optional<nullable<T>>
- .optional() → optional<T>
- Always present fields → T (not optional)
- Update backend-dev-guidelines skill with Fern API sync guidelines
- Add API Documentation section to REVIEW.md
Closes#11232🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: patch
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Add configurable async_insert_busy_timeout_min_ms for ClickHouse client.
The setting is optional and when provided must be >= 50ms.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
When users log in via SSO (e.g., Keycloak) with allowDangerousEmailAccountLinking
enabled, existing users were not being assigned to the default org/project because
createProjectMembershipsOnSignup was only called from createUser, not linkAccount.
This fix:
- Changes all prisma.create() calls to prisma.upsert() with update: {} to make
the function idempotent and preserve existing roles
- Calls createProjectMembershipsOnSignup from linkAccount so SSO users with
pre-existing accounts get default memberships assigned
Fixes#10907🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Previously, the public API endpoint for blob storage integrations stored
secretAccessKey in plaintext, while the tRPC endpoint correctly encrypted it.
Changes:
- Encrypt secretAccessKey before storing in public API endpoint
- Add background migration to encrypt existing unencrypted secrets
- Add test verifying encryption works correctly
The background migration detects unencrypted values by attempting to decrypt
them - if decryption fails with "Invalid or corrupted cipher format", the
value is unencrypted and needs encryption. This is reliable because cloud
provider secrets (AWS/Azure/GCP) never contain colons, which are required
in the encrypted format (iv:encrypted:authTag).
Closes INT-372
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* feat(traces): add refresh button for manual and periodic refresh
* use react-query pattern + add to observations table
* recalc date range on tick
* sanity check values
---------
Co-authored-by: Nimar <l.nimar.b@gmail.com>
fix(worker): improve test isolation for StorageService dependent tests by prexing files with a random value and then deleting all files with that value once the test finishes.
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* feat: Add SSRF protection for PostHog hostname
Co-authored-by: max <max@langfuse.com>
* Refactor PostHog integration tests and add hostname validation
Co-authored-by: max <max@langfuse.com>
* Fix: Remove port from PostHog hostname in tests
Co-authored-by: max <max@langfuse.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
- Updated Trace, TracePreview, and other components to use serverScores instead of scores for clarity.
- Introduced mergedScores in TraceDataContext for better score management.
- Adjusted related components to ensure consistent data handling across the application.
* chore(prisma): rename ScoreDataType to ScoreConfigDataType and update related schema and types
* chore: update score config types
* chore: adjust score types for use cases
* fixup: adjust score types for use cases
* chore: push
* chore: push
* chore: push
* chore: push
* chore: push
* chore(corrections): add `long_string_value` to `scores` table
* fix(migrations): change `long_string_value` column type from Nullable(String) to String in scores table
* chore: add correction type definition and schema
* chore: add correction type to public API
* chore: adjust score types for use cases
* chore: add scores tests for corrections on scores v2 API
* chore: update ingestion and aggreation types
* tests: scores v1 and v2 API
* chore: update API types
* feat: enhance score type handling with data type filtering
* feat: read aggregate score types only by default
* feat: exclude CORRECTION scores in v1 and include in scores v2
* fixup: read aggregate score types only by default
* chore: push
* chore: push
* chore: types
* chore: types
* chore: types
* chore: reorder migrations
* chore: types
* chore: types
* fix: prevent association of CORRECTION scores with sessions and dataset runs
* fixup: test
* fix: test
* chore: schema
* chore: build
* chore: test
* chore: never return long_string_value but string_value for corrections
* test: add
* chore: converter
* chore: test
* fix: API return types
* chore: override correction scores to reference output
* chore: rename
* chore: rm test
* feat: add adaptive virtualization and fix scroll handling for JSON Beta view
- Add virtualization threshold (2500 rows) to IOPreviewJSON
- Split rendering: virtualized (accordion) vs continuous (non-virtualized)
- Fix scroll capture by removing height constraints in non-virtualized mode
- Add onVirtualizationChange callback to parent components
- Create rowCount utility for threshold detection
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(ui): add multi-section JSON viewer with adaptive rendering
Implement MultiSectionJsonViewer component that displays multiple JSON
objects in a single viewer with collapsible sections, sticky headers,
and adaptive virtualization.
Features:
- Multiple JSON roots in one viewer with distinct sections
- Sticky section headers that remain visible during scroll
- Search across all sections with auto-expand on matches
- Per-section line numbering and custom backgrounds
- Adaptive rendering: simple (< 500 nodes) or virtualized (> 500 nodes)
- Supports wrap/nowrap/truncate string modes with proper width handling
- Context API for custom section header/footer components
Implementation:
- MultiSectionJsonViewer: Main component with data/presentation separation
- SimpleMultiSectionViewer: Non-virtualized renderer for small datasets
- VirtualizedMultiSectionViewer: Virtualized renderer for large datasets
- useMultiSectionTreeState: Hook for building and managing section trees
- multiSectionTree utils: Tree construction with section nodes
- SectionContext: React context for section state access
Width handling fixes:
- Scrollable column uses fit-content + minWidth (tree.maxContentWidth)
- Section wrappers use fit-content in nowrap mode for full expansion
- Background color applied at section wrapper level to cover full area
- Proper overflow handling: hidden for truncate/wrap, undefined for nowrap
Integration:
- IOPreviewJSON updated to use MultiSectionJsonViewer for input/output/metadata
- Command-based search UI matching LogViewToolbar styling
- Theme support with per-section background colors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ui): fix virtualization and stable line numbers in multi-section JSON viewer
Refactored VirtualizedMultiSectionViewer to match VirtualizedJsonViewer architecture:
- Removed nested scroll container that broke virtualization
- Fixed absolute positioning for all virtual items (headers, footers, spacers, rows)
- Added stable totalContentWidth calculation instead of reactive measurement
- Increased overscan from 50 to 500 for smoother scrolling
Made section line numbers stable and immutable:
- Section line numbers now assigned once during tree building
- Removed recomputeSectionLineNumbers function (no longer needed)
- Line numbers remain constant regardless of expansion state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* remove overscan
* fix(trace-view): eliminate flicker in JSON Beta viewer when selecting observations
When selecting an observation, the JSON Beta viewer would first render
unparsed JSON strings, then flicker and re-render with parsed data.
This caused a jarring visual transition and unnecessary tree rebuilds.
Root cause: Progressive rendering pattern used fallback (parsedInput ?? input),
causing component to render with raw data while Web Worker was parsing.
Changes:
- Add isWaitingForParsing flag to useParsedObservation hook
- Wait for parsing to complete before rendering IOPreviewJSON
- Show "Parsing data..." loading state (100-300ms typical)
- Remove fallback pattern - use only parsed data
- Remove unused props (input, output, metadata, isLoading, media)
- Fix React hooks rules violation (early return after all hooks)
Result: Single clean render with parsed data, no flicker.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(json-viewer): ensure rows fill container width in multi-section viewer
When container width exceeded calculated content width, rows would only
use content width, creating a white gap on the right side.
Solution: Calculate effectiveRowWidth as max(totalContentWidth, containerWidth)
to ensure rows always fill at least the container width.
Also added minWidth: 100% to content container for consistency.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(json-viewer): eliminate white margin with long content in nowrap mode
When stringWrapMode is "nowrap" and content exceeds container width,
individual rows would grow beyond their set width due to fit-content
children, but the parent container stayed at 100%, creating a white
margin on the right.
Solution: Match VirtualizedJsonViewer's approach:
- Parent width: nowrap ? "fit-content" : "100%"
- Parent minWidth: "100%"
In nowrap mode, parent grows to accommodate wide content, enabling
proper horizontal scrolling. In wrap/truncate modes, parent stays
constrained to 100% width.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(json-viewer): measure actual monospace font width for accurate content sizing
Replaces hardcoded 6.2px character width estimate with actual DOM measurement
of the browser's monospace font at 0.7rem. This eliminates white margin issues
on the right side when viewing long content in virtualized JSON view.
Changes:
- Add useMonospaceCharWidth hook to measure actual rendered character width
- Store measurement in sessionStorage to avoid re-measuring per session
- Integrate measured width into tree building (useTreeState, useMultiSectionTreeState)
- Update VirtualizedMultiSectionViewer to use minWidth + max-content pattern
The measurement adapts to different OS/browser monospace fonts (Menlo, Consolas,
Monaco, etc.) providing accurate width estimation regardless of platform.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: format code with prettier
* fix(json-viewer): enable search in virtualized mode
The debounce effect had an inverted condition that prevented
debouncedSearchQuery from being updated when needsVirtualization=true.
This caused search to appear broken in virtualized mode (datasets >2500 rows).
Root cause: Line 93 had `if (needsVirtualization) return;` which exited
early when virtualization was needed, preventing the search query from
being debounced and passed to MultiSectionJsonViewer.
Fix: Remove the early return condition. Search now works in both
virtualized and non-virtualized modes with 300ms debounce.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(json-viewer): improve height estimation accuracy for wrap mode
Use measured character width to calculate dynamic characters-per-line
instead of hardcoded "80 chars per line". This significantly improves
the virtualizer's initial height estimates, reducing re-measurements
during fast scrolling.
Changes:
- Add charWidth parameter to useJsonViewerLayout
- Calculate available width accounting for indent, key, colon, quotes
- Dynamically compute charsPerLine based on measured font width
- Fall back to 80-char estimate if charWidth unavailable
- Apply to both VirtualizedJsonViewer and VirtualizedMultiSectionViewer
Benefits:
- More accurate initial height estimates for wrapped strings
- Fewer layout shifts during virtualized scrolling
- Smoother performance with large datasets in wrap mode
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(json-viewer): correct wrap mode height estimation for CSS layout
Fix height estimation to match actual CSS white-space: pre-wrap behavior.
All wrapped lines (including continuations) start at the same horizontal
position after the opening quote, not from the left margin.
This improves virtualization accuracy for deeply nested wrapped strings.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(json-viewer): add match count badges to multi-section viewer
Enable per-row match count badges in both virtualized and non-virtualized
multi-section JSON viewers, showing indicators like "3/5" when a row has
multiple search matches.
Changes:
- MultiSectionJsonViewer: Calculate matchCounts using getMatchCountsPerNode()
- VirtualizedMultiSectionViewer: Accept and pass matchCounts to JsonRowScrollable
- SimpleMultiSectionViewer: Accept and pass matchCounts to JsonRowScrollable
This brings multi-section viewer search UX to parity with single-section viewer.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(json-viewer): move match count badges to sticky column to prevent text wrapping
Move match count badges from scrollable column to sticky fixed column
(overlaid on line numbers/expand buttons) to prevent them from consuming
horizontal space and causing premature text wrapping in wrap mode.
Changes:
- JsonRowFixed: Add matchCount/currentMatchIndexInRow props, render badge absolutely positioned
- JsonRowScrollable: Remove badge rendering and unused props
- All viewers: Pass matchCount to JsonRowFixed instead of JsonRowScrollable
Benefits:
- Badge no longer reduces available width for wrapped text
- Badge always visible in sticky column (even when scrolling)
- Consistent position regardless of value length
- No layout shifts when badges appear/disappear
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(trace-view): add section navigation hint bar to JSON viewer
Add a thin navigation bar below the search toolbar that allows quick
jumping to Input, Output, and Metadata sections.
Features:
- Shows "Jump to: Input, Output, Metadata" with clickable section links
- Only displays links for visible sections
- Smooth scroll to section headers on click
- Compact 24px height bar with muted background
- Links styled with hover underline effect
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(trace-view): remove tinted backgrounds in JSON viewer sections
Change section backgrounds from colored tints (light blue, light green,
light purple) to transparent/white in light mode for a cleaner look.
Dark mode section backgrounds remain unchanged (dark slate, dark blue-gray,
dark purple for visual separation).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(trace-view): use clean background for section navigation bar
Change "Jump to:" navigation bar background from bg-muted/30 to bg-background
for a cleaner white appearance that matches the UI.
Section backgrounds remain with their colored tints (blue, green, purple)
for visual separation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(json-viewer): improve section scroll-to behavior for virtualized mode
Add data-section-key attributes to section elements and use querySelector
instead of parsing text content. This ensures scroll-to works correctly in
both virtualized and non-virtualized modes, and scrolls to the actual section
position rather than just making the sticky header visible.
Changes:
- VirtualizedMultiSectionViewer: Add data-section-key to section header divs
- SimpleMultiSectionViewer: Add data-section-key to section wrapper divs
- IOPreviewJSON: Use querySelector with data attribute instead of text matching
Benefits:
- Works reliably in virtualized mode (separate virtual rows)
- Scrolls to actual section position, not just sticky header
- Simpler, more maintainable code
- No text parsing needed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore(json-viewer): remove debug console.log statements
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(json-viewer): add scrollToSection method via ref for multi-section viewers
- Add findSectionHeaderIndex utility to find section headers by key
- Expose scrollToSection via imperative handle in both virtualized and simple viewers
- MultiSectionJsonViewer forwards ref with unified interface
- IOPreviewJSON uses ref-based scrolling instead of querySelector
Works correctly in both virtualized and non-virtualized modes, handling
dynamic section positions as sections expand/collapse.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(json-viewer): use auto scroll behavior instead of smooth for virtualizer
TanStack Virtual doesn't fully support smooth scrolling with dynamic sizing.
Changed from behavior: 'smooth' to 'auto' to avoid scroll failures.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(trace-view): remove extra spacing in section navigation bar
Removed gap-1.5 from section wrapper and added after comma
to tighten spacing between section names.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* test: add NASA audio file and trace creation script for media testing
- Add sounds-of-mars-one-small-step-earth.wav (NASA public domain audio)
- Add create-test-traces-with-media.ts script to generate test traces with
different media attachment permutations (image/audio/document)
- Script creates 7 test traces for UI testing of media buttons feature
Audio file courtesy of NASA (public domain)
Source: https://www.nasa.gov/audio-and-ringtones/🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(json-viewer): add media attachment buttons to section headers
- Add MediaButtonGroup component that displays media buttons grouped by type
(image, audio, video, document) with count badges for multiple files
- Show media buttons in JSON viewer section headers (Input, Output, Metadata)
- Support hover-to-preview and click-to-pin interaction patterns
- Filter and display media by section field
- Update section header to show "N keys" instead of "N rows" with thousands
separator and smaller font size
- Add virtualization badge in navigation bar when data exceeds threshold
- Thread media prop through component hierarchy from IOPreview to section
headers
Media buttons appear only when media attachments exist for a section.
Hovering shows preview, clicking pins it open for interaction.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(json-viewer): show media previews in popover instead of file icons
Replace file icon cards with actual media previews:
- Images: 96x96px preview that opens in new tab on click
- Audio: HTML5 audio player with controls
- Video: HTML5 video player with controls
- Documents: Keep file icon card (no preview available)
Also remove debug console.log statements from hover/click interaction.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(json-viewer): add delay before closing media popover on mouse leave
Add 300ms delay before closing the popover when mouse leaves the button
or popover content. This prevents premature closing when moving the mouse
from the button down to the popover.
- Clear timeout when mouse enters either button or popover content
- Apply same delay to both button and content mouseLeave handlers
- Improves UX by giving users time to move mouse between elements
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* remove lgos
* move media creation to seeder
* add chatml media seeder
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Nimar <l.nimar.b@gmail.com>
* add plan
* add test
* move adapters to shared
* allow unused _vars in worker
* fix more lint
* fix lint
* add tests
* cleanup
* fix test
* no more json column
* don't use metadata
* increase migration version
* fix test
* frontend
* test
* fix test
* fix
* import
* fix seeder
* add seeder data
* unstage
* remove migrations
* new column setup
* update
* spelling
* update
* fixup
* fix type
* update
* fix build
* don't show on public API yet
- Install Playwright Chromium for agent browser review: `pnpm run playwright:install`
Minimum verification matrix:
| Change scope | Minimum verification |
| --- | --- |
| `web/**` only | `pnpm --filter web run lint` + targeted web tests |
| `worker/**` only | `pnpm --filter worker run lint` + targeted worker tests |
| `packages/shared/**` (non-schema) | `pnpm --filter @langfuse/shared run lint` + one targeted web check + one targeted worker check |
| `packages/shared/prisma/**` or `packages/shared/clickhouse/**` | `pnpm --filter @langfuse/shared run lint` + `pnpm run db:generate` + targeted web/worker regressions |
| Public API contract (`web/src/pages/api/public/**`, `web/src/features/public-api/types/**`, `fern/apis/**`) | web lint + targeted server API tests + Fern update/regeneration; never hand-edit `generated/**` |
| Cross-package refactor (`web` + `worker` + `shared`) | `pnpm run lint` + `pnpm run typecheck` + targeted tests per impacted package |
## Repo Rules
- Keep changes scoped; avoid unrelated refactors.
- Prefer package-local implementation details in package `AGENTS.md` files.
- Do not hand-edit generated/build artifacts:
-`generated/*`
-`web/.next/*`
-`web/.next-check/*`
-`*/dist/*`
-`packages/shared/prisma/generated/*`
- Public API contract changes must update Fern sources in `fern/apis/**` and
regenerated outputs; never hand-edit `generated/**`.
- Keep tests independent and parallel-safe.
- For bug fixes, write the failing test first, confirm it fails, then fix the
bug.
- For user-visible frontend changes in `web/**`, review the affected flow in a
real browser with the Playwright MCP server before signoff. Use
`skills/frontend-browser-review/SKILL.md` and `../web/AGENTS.md` for the
browser-review loop.
- Never commit secrets or credentials. Keep `.env*.example` files in sync with
required env vars.
## Shared Agent Setup
-`.agents/AGENTS.md` is the canonical root guide.
- Root `AGENTS.md` is a symlink to `.agents/AGENTS.md`.
- Root `CLAUDE.md` is a compatibility symlink to `AGENTS.md`.
- Shared agent/tool config lives in `config.json` and shared skills live in
`skills/`.
- Project-scoped provider discovery files are generated local artifacts. Edit
the canonical files under `.agents/` instead of editing generated tool
directories by hand.
- If you change `.agents/config.json`, `skills/**`, or the shim-generation
workflow, run:
-`pnpm run agents:sync`
-`pnpm run agents:check`
- Do not commit generated provider config or shim outputs under `.claude/`,
`.cursor/`, `.codex/`, `.vscode/`, or `.mcp.json`.
- Durable cross-tool guidance belongs in root/package `AGENTS.md` files or
`skills/**`, not only in tool-specific config directories.
## Commit, PR, and Release Rules
- Commit messages and PR titles must follow Conventional Commits:
`type(scope): description` or `type: description`.
- PR titles are validated by `.github/workflows/validate-pr-title.yml`.
- In PR descriptions, list impacted packages and executed verification commands.
- Release workflow is managed at root with `pnpm run release`.
- Promote `main` to `production` via
`.github/workflows/promote-main-to-production.yml` or
`pnpm run release:cloud`.
- Do not change release/versioning flow without updating this file and impacted
package guides.
## Git and Tooling Notes
- Use `gh search issues` for GitHub issue search.
- Do not use destructive git commands such as `reset --hard` unless explicitly
requested.
- Do not revert unrelated working-tree changes.
- Keep commits focused and atomic.
- Remaining `.cursor/rules/*.mdc` files should stay thin wrappers around shared
docs or skills rather than owning durable repo guidance directly.
description:Use when editing worker/src/constants/default-model-prices.json, packages/shared/src/server/llm/types.ts, pricing tiers, tokenizer IDs, or matchPattern regexes for OpenAI, Anthropic, Bedrock, Vertex, Azure, or Gemini model pricing.
---
# Add Model Price
Use this skill for model pricing changes in `worker/` and shared LLM type
- Updating provider prices, cache pricing, or tier conditions
- Expanding regex coverage for Bedrock, Vertex, Azure, or provider-prefixed
model names
## How to Read This Skill
- Start with [AGENTS.md](AGENTS.md) for the high-level workflow and helper
scripts.
- Then open only the specific reference file that matches the task.
## Reference Map
| Topic | Read this when | File |
| --- | --- | --- |
| Schema and tier rules | You need the entry shape or pricing-tier invariants | [references/schema-and-tiers.md](references/schema-and-tiers.md) |
| Provider sources and price keys | You need official pricing URLs, per-token conversion, or provider-specific usage keys | [references/provider-sources-and-price-keys.md](references/provider-sources-and-price-keys.md) |
| Match patterns | You are editing `matchPattern` regexes or provider coverage | [references/match-patterns.md](references/match-patterns.md) |
| Workflow and validation | You are applying the end-to-end edit process or checking common mistakes | [references/workflow-and-validation.md](references/workflow-and-validation.md) |
### 9. Keep Fern API Definitions in Sync with TypeScript Types
When modifying public API types in `web/src/features/public-api/types/`, the corresponding Fern API definitions in `fern/apis/server/definition/` must be updated to match.
Service layer overview, dependency injection patterns, singleton patterns, repository pattern for data access, service design principles, caching strategies, testing services
Dual database architecture (PostgreSQL via Prisma + ClickHouse via direct client), PostgreSQL CRUD operations, ClickHouse query patterns (queryClickhouse, queryClickhouseStream, upsertClickhouse), repository pattern for complex queries, tenant isolation with projectId filtering, when to use which database
Environment variable validation with Zod, package-specific configs (web/env.mjs with t3-oss/env-nextjs, worker/env.ts, shared/env.ts), NEXT_PUBLIC_LANGFUSE_CLOUD_REGION usage, LANGFUSE_EE_LICENSE_KEY for enterprise features, best practices for env management
Integration tests (Public API with makeZodVerifiedAPICall), tRPC tests (createInnerTRPCContext, appRouter.createCaller), service-level tests (repository/service functions), worker tests (vitest with streams), test isolation principles, running tests (Jest for web, vitest for worker)
---
## Related Skills
- **database-verification** - Verify column names and schema consistency
- **skill-developer** - Meta-skill for creating and managing skills
description:Shared backend guide for Langfuse's Next.js 14, tRPC, BullMQ, and TypeScript monorepo. Use when creating or reviewing tRPC routers, public REST endpoints, BullMQ queue processors, backend services, middleware, Prisma or ClickHouse data access, OpenTelemetry instrumentation, Zod validation, env configuration, or backend tests across web, worker, or packages/shared.
---
# Backend Development Guidelines
Use this skill for backend and API work across `web/`, `worker/`, and
`packages/shared/`.
## When to Apply
- Creating or modifying tRPC routers and procedures
- Creating or modifying public API endpoints
- Creating or modifying queue processors, producers, or queue-backed workflows
- Building or refactoring backend services and repositories
- Working on backend auth, middleware, validation, or observability
- Updating Prisma or ClickHouse access patterns
- Adding or fixing backend tests
## How to Read This Skill
- Start with [AGENTS.md](AGENTS.md) when the task spans multiple backend areas
or you need the end-to-end checklists.
- Read only the specific reference file that matches the work when the scope is
narrower.
## Reference Map
| Topic | Read this when | File |
| --- | --- | --- |
| Architecture and package boundaries | You need the web/worker/shared split, request flow, or queue lifecycle | [references/architecture-overview.md](references/architecture-overview.md) |
| Routing and controllers | You are writing tRPC procedures, public API routes, or queue entrypoints | [references/routing-and-controllers.md](references/routing-and-controllers.md) |
| Middleware and auth | You are changing request auth, permissions, or middleware composition | [references/middleware-guide.md](references/middleware-guide.md) |
| Services and repositories | You are placing business logic, repository code, or DI patterns | [references/services-and-repositories.md](references/services-and-repositories.md) |
| Database access | You are touching Prisma, ClickHouse, tenant filters, or query patterns | [references/database-patterns.md](references/database-patterns.md) |
| Configuration | You are adding env vars, startup config, or runtime toggles | [references/configuration.md](references/configuration.md) |
| Testing | You are adding or updating backend tests | [references/testing-guide.md](references/testing-guide.md) |
## Full Compiled Guide
Read [AGENTS.md](AGENTS.md) for the complete backend guide with checklists,
directory conventions, imports, architecture, and cross-cutting practices.
description:MUST USE when reviewing ClickHouse schemas, queries, or configurations. Contains 28 rules that MUST be checked before providing recommendations. Always read relevant rule files and cite specific rules in responses.
license:Apache-2.0
metadata:
author:ClickHouse Inc
version:"0.3.0"
---
# ClickHouse Best Practices
Comprehensive guidance for ClickHouse covering schema design, query optimization, and data ingestion. Contains 28 rules across 3 main categories (schema, query, insert), prioritized by impact.
> **Official docs:** [ClickHouse Best Practices](https://clickhouse.com/docs/best-practices)
## IMPORTANT: How to Apply This Skill
**Before answering ClickHouse questions, follow this priority order:**
1.**Check for applicable rules** in the `rules/` directory
2.**If rules exist:** Apply them and cite them in your response using "Per `rule-name`..."
3.**If no rule exists:** Use the LLM's ClickHouse knowledge or search documentation
4.**If uncertain:** Use web search for current best practices
5.**Always cite your source:** rule name, "general ClickHouse guidance", or URL
**Why rules take priority:** ClickHouse has specific behaviors (columnar storage, sparse indexes, merge tree mechanics) where general database intuition can be misleading. The rules encode validated, ClickHouse-specific guidance.
### For Formal Reviews
When performing a formal review of schemas, queries, or data ingestion:
---
## Review Procedures
### For Schema Reviews (CREATE TABLE, ALTER TABLE)
**Read these rule files in order:**
1.`rules/schema-pk-plan-before-creation.md` - ORDER BY is immutable
2.`rules/schema-pk-cardinality-order.md` - Column ordering in keys
This file defines all sections, their ordering, impact levels, and descriptions.
The section ID (in parentheses) is the filename prefix used to group rules.
---
## 1. Schema Design (schema)
**Impact:** CRITICAL
**Description:** Proper schema design is foundational to ClickHouse performance. ORDER BY is immutable after table creation; wrong choices require full data migration. Includes primary key selection, data types, partitioning strategy, and JSON usage. Column types and ordering can impact query speed by orders of magnitude.
## 2. Query Optimization (query)
**Impact:** CRITICAL
**Description:** Query patterns dramatically affect performance. JOIN algorithms, filtering strategies, skipping indices, and materialized views can reduce query time from minutes to milliseconds. Pre-computed aggregations read thousands of rows instead of billions.
## 3. Insert Strategy (insert)
**Impact:** CRITICAL
**Description:** Each INSERT creates a data part. Single-row inserts overwhelm the merge process. Proper batching (10K-100K rows), async inserts for high-frequency writes, mutation avoidance, and letting background merges work are essential for stable cluster performance.
impactDescription:"Each INSERT creates a part; single-row inserts overwhelm merge process"
tags:[insert, batching, parts, performance]
---
## Batch Inserts Appropriately (10K-100K rows)
**Impact: CRITICAL**
Each INSERT creates a new data part. Single-row or small-batch inserts create thousands of tiny parts, overwhelming the merge process and causing cluster instability.
**Incorrect (single-row or tiny batches):**
```python
# Single-row inserts - creates 10,000 parts!
foreventinevents:
client.execute("INSERT INTO events VALUES",[event])
# Tiny batches - still too many parts
forbatchinchunks(events,100):# 100 rows per INSERT
client.execute("INSERT INTO events VALUES",batch)
```
**Correct (proper batch size):**
```python
# Ideal batch size: 10,000-100,000 rows
BATCH_SIZE=10_000
forbatchinchunks(events,BATCH_SIZE):
client.execute("INSERT INTO events VALUES",batch)
```
**Recommended batch sizes:**
| Threshold | Value |
|-----------|-------|
| Minimum | 1,000 rows |
| Ideal range | 10,000-100,000 rows |
| Insert rate (sync) | ~1 insert per second |
**Validation:**
```sql
-- Monitor part count (>3000 per partition blocks inserts)
SELECTtable,count()asparts,sum(rows)astotal_rows
FROMsystem.parts
WHEREactiveANDdatabase='default'
GROUPBYtable
ORDERBYpartsDESC;
```
Reference: [Selecting an Insert Strategy](https://clickhouse.com/docs/best-practices/selecting-an-insert-strategy)
`ALTER TABLE UPDATE` is a mutation - an asynchronous background process that rewrites entire data parts affected by the change. This is extremely expensive for frequent or large-scale operations.
**Why mutations are problematic:**
- **Write amplification:** Rewrite complete parts even for minor changes
impactDescription:"Forces expensive merge of all parts; let background merges work"
tags:[insert, OPTIMIZE, merge, performance]
---
## Avoid OPTIMIZE TABLE FINAL
**Impact: HIGH**
`OPTIMIZE TABLE ... FINAL` forces immediate merge of all parts into one part per partition. This is resource-intensive and rarely necessary. ClickHouse already performs smart background merges.
**Note:**`OPTIMIZE FINAL` is not the same as `FINAL`. The `FINAL` modifier in SELECT queries may be necessary for deduplicated results in ReplacingMergeTree and is generally fine to use.
**Incorrect (OPTIMIZE FINAL after inserts):**
```sql
-- Running OPTIMIZE FINAL after every batch insert
INSERTINTOeventsSELECT*FROMstaging_events;
OPTIMIZETABLEeventsFINAL;-- Expensive and unnecessary!
title:Use Data Skipping Indices for Non-ORDER BY Filters
impact:HIGH
impactDescription:"Up to 60x faster queries by skipping irrelevant granules"
tags:[query, index, skipping, bloom_filter]
---
## Use Data Skipping Indices for Non-ORDER BY Filters
**Impact: HIGH**
Queries filtering on columns not in ORDER BY cannot use the primary index and result in full scans. Data skipping indices store metadata about blocks and skip granules that definitely don't match.
**Important:** Skip indices should be considered **after** optimizing data types, primary key selection, and materialized views.
**When to use:**
- High overall cardinality but low cardinality within blocks
- Rare values critical for search (error codes, specific IDs)
- Column correlates with primary key
**When NOT to use:**
- As a first optimization step
- Matching values scattered across many blocks
- Without testing on real data
**Incorrect (filtering on non-ORDER BY column):**
```sql
CREATETABLEevents(
event_typeLowCardinality(String),
timestampDateTime,
user_idUInt64-- Not in ORDER BY
)
ENGINE=MergeTree()
ORDERBY(event_type,toDate(timestamp));
-- Query filters on user_id - scans all matching event_type
**Note:** ClickHouse 24.12+ automatically positions smaller tables on the right side. For earlier versions, manually ensure the smaller table is on the RIGHT.
Reference: [Minimize and Optimize JOINs](https://clickhouse.com/docs/best-practices/minimize-optimize-joins)
impactDescription:"Dictionaries and denormalization shift work from query time to insert time"
tags:[query, JOIN, dictionary, denormalization]
---
## Consider Alternatives to JOINs
**Impact: CRITICAL**
Repeated JOINs to dimension tables add overhead. Dictionaries or denormalization shift computational work from query time to insert/pre-processing time.
**Incorrect (JOIN on every query):**
```sql
-- JOIN on every query
SELECTo.order_id,c.name,c.email
FROMorderso
JOINcustomerscONc.id=o.customer_id
WHEREo.created_at>'2024-01-01';
```
**Correct - Dictionary Lookup:**
```sql
-- Create dictionary
CREATEDICTIONARYcustomer_dict(
idUInt64,
nameString,
emailString
)
PRIMARYKEYid
SOURCE(CLICKHOUSE(TABLE'customers'))
LAYOUT(HASHED())
LIFETIME(MIN300MAX360);
-- Use dictGet instead of JOIN (uses direct join algorithm - fastest)
| Dictionary | Frequent lookups to small dimension | Fastest (in-memory) |
| Denormalization | Analytics always need enriched data | Fast (no join at query) |
| IN subquery | Existence filtering | Often faster than JOIN |
| JOIN | Infrequent or complex joins | Acceptable |
**Critical dictionary caveat:** Dictionaries silently deduplicate duplicate keys, retaining only the final value. Only use when source has unique keys.
Reference: [Minimize and Optimize JOINs](https://clickhouse.com/docs/best-practices/minimize-optimize-joins)
impactDescription:"Joining full tables then filtering wastes resources"
tags:[query, JOIN, filtering, subquery]
---
## Filter Tables Before Joining
**Impact: CRITICAL**
Joining full tables then filtering wastes resources. Add filtering in `WHERE` or `JOIN ON` clauses. If automatic pushdown fails, restructure as a subquery.
**Incorrect (join then filter):**
```sql
-- Joins entire tables, then filters
SELECTo.order_id,c.name,o.total
FROMorderso
JOINcustomerscONc.id=o.customer_id
WHEREo.created_at>'2024-01-01'ANDc.country='US';
```
**Correct (filter in subqueries before joining):**
```sql
-- Filter in subqueries before joining
SELECTo.order_id,c.name,o.total
FROM(
SELECTorder_id,customer_id,total
FROMorders
WHEREcreated_at>'2024-01-01'
)o
JOIN(
SELECTid,name
FROMcustomers
WHEREcountry='US'
)cONc.id=o.customer_id;
```
**Even better - aggregate before joining:**
```sql
SELECTc.country,o.total_revenue
FROM(
SELECTcustomer_id,sum(total)astotal_revenue
FROMorders
WHEREcreated_at>'2024-01-01'
GROUPBYcustomer_id
)o
JOINcustomerscONc.id=o.customer_id;
```
Reference: [Minimize and Optimize JOINs](https://clickhouse.com/docs/best-practices/minimize-optimize-joins)
Incremental MVs automatically apply the view's query to new data blocks at insert time. Results are written to a target table and partial results merge over time.
**Incorrect (full aggregation on every query):**
```sql
-- Full aggregation on every dashboard load
SELECT
event_type,
toStartOfHour(timestamp)ashour,
count()asevents,
uniq(user_id)asunique_users
FROMevents
WHEREtimestamp>=now()-INTERVAL7DAY
GROUPBYevent_type,hour;
-- Scans 7 days of data every time (billions of rows)
```
**Correct (incremental MV with pre-aggregation):**
```sql
-- Create target table for aggregated data
CREATETABLEevents_hourly(
event_typeLowCardinality(String),
hourDateTime,
eventsAggregateFunction(count),
unique_usersAggregateFunction(uniq,UInt64)
)
ENGINE=AggregatingMergeTree()
ORDERBY(event_type,hour);
-- Create materialized view to populate incrementally
impactDescription:"Field-level querying for semi-structured data; use typed columns for known schemas"
tags:[schema, JSON, semi-structured, flexibility]
---
## Use JSON Type for Dynamic Schemas
**Impact: MEDIUM**
ClickHouse's JSON type splits JSON objects into separate sub-columns, enabling field-level query optimization. Use it for truly dynamic data, not everything.
**Incorrect (schema bloat or opaque String):**
```sql
-- BAD: Hundreds of nullable columns for event properties
CREATETABLEevents(
event_idUUID,
prop_page_urlNullable(String),
prop_button_idNullable(String),
-- ... 100 more nullable columns
)
-- BAD: JSON as String when you need field queries
CREATETABLEevents(
event_idUUID,
propertiesString-- No field-level optimization
)
```
**Correct (JSON for dynamic, typed for known):**
```sql
-- Use JSON type for dynamic properties
CREATETABLEevents(
event_idUUIDDEFAULTgenerateUUIDv4(),
event_typeLowCardinality(String),
timestampDateTimeDEFAULTnow(),
propertiesJSON-- Flexible schema with type inference
Too many distinct partition values create excessive data parts, eventually triggering "too many parts" errors. ClickHouse enforces limits via `max_parts_in_total` and `parts_to_throw_insert` settings.
**Incorrect (high cardinality partitioning):**
```sql
-- High cardinality = too many partitions
CREATETABLEevents(...)
ENGINE=MergeTree()
PARTITIONBYuser_id-- Millions of partitions!
ORDERBY(timestamp);
-- Daily partitions can grow unbounded over years
CREATETABLElogs(...)
ENGINE=MergeTree()
PARTITIONBYtoDate(timestamp)-- 3650 partitions over 10 years
ORDERBY(service,timestamp);
```
**Correct (bounded cardinality):**
```sql
-- Monthly partitions = 12 per year, bounded cardinality
CREATETABLEevents(
timestampDateTime,
event_typeLowCardinality(String),
user_idUInt64
)
ENGINE=MergeTree()
PARTITIONBYtoStartOfMonth(timestamp)
ORDERBY(event_type,timestamp);
```
**Validation:**
```sql
-- Check partition count and health
SELECT
partition,
count()asparts,
sum(rows)asrows,
formatReadableSize(sum(bytes_on_disk))assize
FROMsystem.parts
WHEREtable='events'ANDactive
GROUPBYpartition
ORDERBYpartition;
-- Warning signs: hundreds or thousands of partitions
```
Reference: [Choosing a Partitioning Key](https://clickhouse.com/docs/best-practices/choosing-a-partitioning-key)
impactDescription:"Enables granule skipping; high-cardinality first prevents index pruning"
tags:[schema, primary-key, cardinality, ORDER BY]
---
## Order Columns by Cardinality (Low to High)
**Impact: CRITICAL**
Since the sparse primary index operates on data blocks (granules) rather than individual rows, low-cardinality leading columns create more useful index entries that can skip entire blocks. Place lower-cardinality columns before higher-cardinality ones in the ordering key.
**Incorrect (high cardinality first):**
```sql
-- UUID first means no pruning benefit
CREATETABLEevents(...)
ENGINE=MergeTree()
ORDERBY(event_id,event_type,timestamp);
-- Every granule has different event_id values, index can't skip anything
| 2nd | Date (coarse granularity) | toDate(timestamp) |
| 3rd+ | Medium-High | user_id, session_id |
| Last | High (if needed) | event_id, uuid |
**Tip:** Use `toDate(timestamp)` instead of raw `DateTime` columns when day-level filtering suffices - this reduces index size from 32-bit to 16-bit representations.
Reference: [Choosing a Primary Key](https://clickhouse.com/docs/best-practices/choosing-a-primary-key)
impactDescription:"Skipping prefix columns prevents index usage"
tags:[schema, primary-key, WHERE, query]
---
## Filter on ORDER BY Columns in Queries
**Impact: CRITICAL**
Even with good schema design, queries must use ORDER BY columns to benefit. Skipping prefix columns or filtering on non-ORDER BY columns prevents index usage.
**Incorrect (skips prefix or uses non-ORDER BY columns):**
```sql
-- Given: ORDER BY (tenant_id, event_type, timestamp)
-- Skips prefix columns - can't use index effectively
SELECT*FROMeventsWHEREevent_type='click';
-- Filter on column not in ORDER BY - full table scan
SELECT*FROMeventsWHEREuser_agentLIKE'%Chrome%';
```
**Correct (uses ORDER BY prefix):**
```sql
-- Given: ORDER BY (tenant_id, event_type, timestamp)
-- Full prefix match - best performance
SELECT*FROMevents
WHEREtenant_id=123ANDevent_type='click';
-- Partial prefix - still uses index
SELECT*FROMeventsWHEREtenant_id=123;
-- Range on later column after equality on earlier
impactDescription:"ORDER BY is immutable; wrong choice requires full data migration"
tags:[schema, primary-key, ORDER BY]
---
## Plan PRIMARY KEY Before Table Creation
**Impact: CRITICAL** (immutable after creation)
ClickHouse's ORDER BY clause defines physical data ordering and the sparse index. Unlike other databases, **ORDER BY cannot be modified after table creation**. A wrong choice requires creating a new table and migrating all data.
**Incorrect (arbitrary ORDER BY without query analysis):**
```sql
-- Creating table without analyzing query patterns
CREATETABLEevents(
event_idUUID,
user_idUInt64,
timestampDateTime
)
ENGINE=MergeTree()
ORDERBY(event_id);-- Chosen arbitrarily
-- Later: "Most queries filter by user_id!"
-- Cannot fix with: ALTER TABLE events MODIFY ORDER BY (user_id, timestamp)
-- ERROR: Cannot modify ORDER BY
```
**Correct (query-driven ORDER BY selection):**
```sql
-- Step 1: Document query patterns BEFORE creating table
/*
Query Analysis:
- 60% of queries: WHERE user_id = ? AND timestamp BETWEEN ? AND ?
- 25% of queries: WHERE event_type = ? AND timestamp > ?
- 15% of queries: WHERE event_id = ?
Conclusion: user_id and event_type are primary filters
*/
-- Step 2: Create table with correct ORDER BY
CREATETABLEevents(
event_idUUIDDEFAULTgenerateUUIDv4(),
user_idUInt64,
event_typeLowCardinality(String),
timestampDateTime,
event_dateDateDEFAULTtoDate(timestamp)
)
ENGINE=MergeTree()
PARTITIONBYtoYYYYMM(event_date)
ORDERBY(user_id,event_date,event_id);
```
**Pre-creation checklist:**
- [ ] Listed top 5-10 query patterns
- [ ] Identified columns in WHERE clauses with frequency
- [ ] Prioritized columns that exclude large numbers of rows
- [ ] Ordered columns by cardinality (low first, high last)
- [ ] Limited to 4-5 key columns (typically sufficient)
Reference: [Choosing a Primary Key](https://clickhouse.com/docs/best-practices/choosing-a-primary-key)
impactDescription:"Columns not in ORDER BY cause full table scans"
tags:[schema, primary-key, WHERE, filtering]
---
## Prioritize Filter Columns in ORDER BY
**Impact: CRITICAL**
Prioritize columns frequently used in query filters (WHERE clause), especially those that exclude large numbers of rows. Queries filtering on columns not in ORDER BY result in full table scans.
**Incorrect (ORDER BY doesn't match query patterns):**
```sql
-- If most queries filter by tenant_id:
CREATETABLEevents(...)
ENGINE=MergeTree()
ORDERBY(event_id);-- Queries by tenant_id will full-scan!
impactDescription:"Nullable adds storage overhead; use DEFAULT values instead"
tags:[schema, data-types, Nullable, DEFAULT]
---
## Avoid Nullable Unless Semantically Required
**Impact: HIGH**
Nullable columns maintain a separate UInt8 column for tracking null values, increasing storage and degrading performance. Use DEFAULT values instead when feasible.
**Incorrect (Nullable everywhere):**
```sql
CREATETABLEusers(
idNullable(UInt64),-- IDs should never be null
nameNullable(String),-- Empty string is fine
ageNullable(UInt8),-- 0 is a valid default
login_countNullable(UInt32)-- 0 is a valid default
)
```
**Correct (DEFAULT values, Nullable only when semantic):**
```sql
CREATETABLEusers(
idUInt64,-- Never null
nameStringDEFAULT'',-- Empty = unknown
ageUInt8DEFAULT0,-- 0 = unknown
login_countUInt32DEFAULT0,-- 0 = never logged in
deleted_atNullable(DateTime),-- NULL = not deleted (semantic!)
parent_idNullable(UInt64)-- NULL = no parent (semantic!)
impactDescription:"Insert-time validation and natural ordering; 1-2 bytes storage"
tags:[schema, data-types, Enum, validation]
---
## Use Enum for Finite Value Sets
**Impact: MEDIUM**
Enum types provide validation at insert time and enable queries that exploit natural ordering. Use Enum8 (up to 256 values) or Enum16 (up to 65,536 values).
**Incorrect (String without validation):**
```sql
CREATETABLEorders(
statusString-- No validation, typos like "shiped" allowed
Reserve `FixedString` for strictly fixed-length data (e.g., 2-char country codes). For most low-cardinality text, `LowCardinality(String)` outperforms `FixedString`.
impactDescription:"2-10x storage reduction; enables compression and correct semantics"
tags:[schema, data-types, storage]
---
## Use Native Types Instead of String
**Impact: CRITICAL**
Using String for all data wastes storage, prevents compression optimization, and makes comparisons slower. ClickHouse's column-oriented architecture benefits directly from optimal type selection.
This is the canonical shared review checklist for Langfuse.
## Database Migrations
@@ -10,6 +12,7 @@
- Migrations in `packages/shared/clickhouse/migrations/clustered` should match their counterparts in `packages/shared/clickhouse/migrations/unclustered` aside from the restrictions listed above.
- When adding new indexes on ClickHouse, ensure that there is a corresponding `MATERIALIZE INDEX` statement in the same migration. The materialization can use `SETTINGS mutations_sync = 2` if they operate on smaller tables, but may timeout otherwise.
- All ClickHouse queries on project-scoped tables (traces, observations, scores, events, sessions, etc.) must include `WHERE project_id = {projectId: String}` filter to ensure proper tenant isolation and that queries only access data from the intended project.
- For operations on the `events` table, you must never use the `FINAL` keyword as it kills performance. `events` is built so that `FINAL` is never required.
### Postgres
@@ -45,3 +48,10 @@
## Seeder
- make sure that for new features with data model changes, the database seeder is adjusted.
## API Documentation
- Whenever a file in `web/src/features/public-api/types` changes, the `fern/apis` definition probably needs to be adjusted, too.
- `nullish` types should map to `optional<nullable<T>>` in fern.
- `nullable` types should map to `nullable<T>` in fern.
- `optional` types should map to `optional<T>` in fern.
description:Use when upgrading a dependency in this pnpm workspace, including requests to bump a package to a specific version, compare the registry latest version with the latest version installable under the current minimum-release-age window, or decide whether minimumReleaseAgeExclude in pnpm-workspace.yaml must change. Ask the user for the package name or target version when either is missing.
---
# PNPM Upgrade Package
Use this skill for interactive dependency bumps in Langfuse.
## Read Order
- Start with [AGENTS.md](AGENTS.md) for the end-to-end workflow.
- Run the main helper once at the start of the upgrade:
default_prompt:"Use $pnpm-upgrade-package to upgrade a package in this pnpm workspace, asking me for the package or version if I did not provide them."
// Root package.json - ONLY delegates, no task logic
{
"scripts":{
"build":"turbo run build",
"lint":"turbo run lint",
"test":"turbo run test"
}
}
```
```json
// DO NOT DO THIS - defeats parallelization
// Root package.json
{
"scripts":{
"build":"cd apps/web && next build && cd ../api && tsc",
"lint":"eslint apps/ packages/",
"test":"vitest"
}
}
```
Root Tasks (`//#taskname`) are ONLY for tasks that truly cannot exist in packages (rare).
## Secondary Rule: `turbo run` vs `turbo`
**Always use `turbo run` when the command is written into code:**
```json
// package.json - ALWAYS "turbo run"
{
"scripts":{
"build":"turbo run build"
}
}
```
```yaml
# CI workflows - ALWAYS "turbo run"
- run:turbo run build --affected
```
**The shorthand `turbo <tasks>` is ONLY for one-off terminal commands** typed directly by humans or agents. Never write `turbo build` into package.json, CI, or scripts.
"changeset:publish":"turbo run build && changeset publish"
}
}
```
### `prebuild` Scripts That Manually Build Dependencies
Scripts like `prebuild` that manually build other packages bypass Turborepo's dependency graph.
```json
// WRONG - manually building dependencies
{
"scripts":{
"prebuild":"cd ../../packages/types && bun run build && cd ../utils && bun run build",
"build":"next build"
}
}
```
**However, the fix depends on whether workspace dependencies are declared:**
1.**If dependencies ARE declared** (e.g., `"@repo/types": "workspace:*"` in package.json), remove the `prebuild` script. Turbo's `dependsOn: ["^build"]` handles this automatically.
2.**If dependencies are NOT declared**, the `prebuild` exists because `^build` won't trigger without a dependency relationship. The fix is to:
- Add the dependency to package.json: `"@repo/types": "workspace:*"`
- Then remove the `prebuild` script
```json
// CORRECT - declare dependency, let turbo handle build order
// package.json
{
"dependencies":{
"@repo/types":"workspace:*",
"@repo/utils":"workspace:*"
},
"scripts":{
"build":"next build"
}
}
// turbo.json
{
"tasks":{
"build":{
"dependsOn":["^build"]
}
}
}
```
**Key insight:**`^build` only runs build in packages listed as dependencies. No dependency declaration = no automatic build ordering.
### Overly Broad `globalDependencies`
`globalDependencies` affects ALL tasks in ALL packages via the **global hash** — tasks cannot opt out of specific files, even with negation globs in `inputs`. Be specific.
```json
// WRONG - heavy hammer, affects all hashes
{
"globalDependencies":["**/.env.*local"]
}
// BETTER - move to task-level inputs
{
"globalDependencies":[".env"],
"tasks":{
"build":{
"inputs":["$TURBO_DEFAULT$",".env*"],
"outputs":["dist/**"]
}
}
}
```
With `futureFlags.globalConfiguration`, this problem is reduced because `global.inputs` files are folded into each task's inputs (not the global hash). Tasks can exclude specific files:
```json
// BEST - global.inputs with per-task exclusion
{
"futureFlags":{"globalConfiguration":true},
"global":{
"inputs":[".env"]
},
"tasks":{
"build":{"outputs":["dist/**"]},
"lint":{
"inputs":["$TURBO_DEFAULT$","!$TURBO_ROOT$/.env"]
}
}
}
```
### Repetitive Task Configuration
Look for repeated configuration across tasks that can be collapsed. Turborepo supports shared configuration patterns.
```json
// WRONG - repetitive env and inputs across tasks
{
"tasks":{
"build":{
"env":["API_URL","DATABASE_URL"],
"inputs":["$TURBO_DEFAULT$",".env*"]
},
"test":{
"env":["API_URL","DATABASE_URL"],
"inputs":["$TURBO_DEFAULT$",".env*"]
},
"dev":{
"env":["API_URL","DATABASE_URL"],
"inputs":["$TURBO_DEFAULT$",".env*"],
"cache":false,
"persistent":true
}
}
}
// BETTER - use globalEnv and globalDependencies for shared config
{
"globalEnv":["API_URL","DATABASE_URL"],
"globalDependencies":[".env*"],
"tasks":{
"build":{},
"test":{},
"dev":{
"cache":false,
"persistent":true
}
}
}
```
**When to use global vs task-level:**
-`globalEnv` / `globalDependencies` - affects ALL tasks, use for truly shared config
- Task-level `env` / `inputs` - use when only specific tasks need it
### NOT an Anti-Pattern: Large `env` Arrays
A large `env` array (even 50+ variables) is **not** a problem. It usually means the user was thorough about declaring their build's environment dependencies. Do not flag this as an issue.
### Using `--parallel` Flag
The `--parallel` flag bypasses Turborepo's dependency graph. If tasks need parallel execution, configure `dependsOn` correctly instead.
```bash
# WRONG - bypasses dependency graph
turbo run lint --parallel
# CORRECT - configure tasks to allow parallel execution
# In turbo.json, set dependsOn appropriately (or use transit nodes)
turbo run lint
```
### Package-Specific Task Overrides in Root turbo.json
When multiple packages need different task configurations, use **Package Configurations** (`turbo.json` in each package) instead of cluttering root `turbo.json` with `package#task` overrides.
```json
// WRONG - root turbo.json with many package-specific overrides
**Before flagging missing `outputs`, check what the task actually produces:**
1. Read the package's script (e.g., `"build": "tsc"`, `"test": "vitest"`)
2. Determine if it writes files to disk or only outputs to stdout
3. Only flag if the task produces files that should be cached
```json
// WRONG: build produces files but they're not cached
{
"tasks":{
"build":{
"dependsOn":["^build"]
}
}
}
// CORRECT: build outputs are cached
{
"tasks":{
"build":{
"dependsOn":["^build"],
"outputs":["dist/**"]
}
}
}
```
Common outputs by framework:
- Next.js: `[".next/**", "!.next/cache/**"]`
- Vite/Rollup: `["dist/**"]`
- tsc: `["dist/**"]` or custom `outDir`
**TypeScript `--noEmit` can still produce cache files:**
When `incremental: true` in tsconfig.json, `tsc --noEmit` writes `.tsbuildinfo` files even without emitting JS. Check the tsconfig before assuming no outputs:
```json
// If tsconfig has incremental: true, tsc --noEmit produces cache files
{
"tasks":{
"typecheck":{
"outputs":["node_modules/.cache/tsbuildinfo.json"]// or wherever tsBuildInfoFile points
}
}
}
```
To determine correct outputs for TypeScript tasks:
1. Check if `incremental` or `composite` is enabled in tsconfig
2. Check `tsBuildInfoFile` for custom cache location (default: alongside `outDir` or in project root)
3. If no incremental mode, `tsc --noEmit` produces no files
### `^build` vs `build` Confusion
```json
{
"tasks":{
// ^build = run build in DEPENDENCIES first (other packages this one imports)
"build":{
"dependsOn":["^build"]
},
// build (no ^) = run build in SAME PACKAGE first
"test":{
"dependsOn":["build"]
},
// pkg#task = specific package's task
"deploy":{
"dependsOn":["web#build"]
}
}
}
```
### Environment Variables Not Hashed
```json
// WRONG: API_URL changes won't cause rebuilds
{
"tasks":{
"build":{
"outputs":["dist/**"]
}
}
}
// CORRECT: API_URL changes invalidate cache
{
"tasks":{
"build":{
"outputs":["dist/**"],
"env":["API_URL","API_KEY"]
}
}
}
```
### `.env` Files Not in Inputs
Turbo does NOT load `.env` files - your framework does. But Turbo needs to know about changes:
```json
// WRONG: .env changes don't invalidate cache
{
"tasks":{
"build":{
"env":["API_URL"]
}
}
}
// CORRECT: .env file changes invalidate cache
{
"tasks":{
"build":{
"env":["API_URL"],
"inputs":["$TURBO_DEFAULT$",".env",".env.*"]
}
}
}
```
### Root `.env` File in Monorepo
A `.env` file at the repo root is an anti-pattern — even for small monorepos or starter templates. It creates implicit coupling between packages and makes it unclear which packages depend on which variables.
```
// WRONG - root .env affects all packages implicitly
my-monorepo/
├── .env # Which packages use this?
├── apps/
│ ├── web/
│ └── api/
└── packages/
// CORRECT - .env files in packages that need them
my-monorepo/
├── apps/
│ ├── web/
│ │ └── .env # Clear: web needs DATABASE_URL
│ └── api/
│ └── .env # Clear: api needs API_KEY
└── packages/
```
**Problems with root `.env`:**
- Unclear which packages consume which variables
- All packages get all variables (even ones they don't need)
- Cache invalidation is coarse-grained (root .env change invalidates everything)
- Security risk: packages may accidentally access sensitive vars meant for others
- Bad habits start small — starter templates should model correct patterns
**If you must share variables**, use `globalEnv` to be explicit about what's shared, and document why.
### Strict Mode Filtering CI Variables
By default, Turborepo filters environment variables to only those in `env`/`globalEnv`. CI variables may be missing:
```json
// If CI scripts need GITHUB_TOKEN but it's not in env:
{
"globalPassThroughEnv":["GITHUB_TOKEN","CI"],
"tasks":{...}
}
```
Or use `--env-mode=loose` (not recommended for production).
### Shared Code in Apps (Should Be a Package)
```
// WRONG: Shared code inside an app
apps/
web/
shared/ # This breaks monorepo principles!
utils.ts
// CORRECT: Extract to a package
packages/
utils/
src/utils.ts
```
### Accessing Files Across Package Boundaries
```typescript
// WRONG: Reaching into another package's internals
Add a `transit` task if you have tasks that need parallel execution with cache invalidation (see below).
### Dev Task with `^dev` Pattern (for `turbo watch`)
A `dev` task with `dependsOn: ["^dev"]` and `persistent: false` in root turbo.json may look unusual but is **correct for `turbo watch` workflows**:
```json
// Root turbo.json
{
"tasks":{
"dev":{
"dependsOn":["^dev"],
"cache":false,
"persistent":false// Packages have one-shot dev scripts
}
}
}
// Package turbo.json (apps/web/turbo.json)
{
"extends":["//"],
"tasks":{
"dev":{
"persistent":true// Apps run long-running dev servers
}
}
}
```
**Why this works:**
- **Packages** (e.g., `@acme/db`, `@acme/validators`) have `"dev": "tsc"` — one-shot type generation that completes quickly
- **Apps** override with `persistent: true` for actual dev servers (Next.js, etc.)
- **`turbo watch`** re-runs the one-shot package `dev` scripts when source files change, keeping types in sync
**Intended usage:** Run `turbo watch dev` (not `turbo run dev`). Watch mode re-executes one-shot tasks on file changes while keeping persistent tasks running.
**Alternative pattern:** Use a separate task name like `prepare` or `generate` for one-shot dependency builds to make the intent clearer:
```json
{
"tasks":{
"prepare":{
"dependsOn":["^prepare"],
"outputs":["dist/**"]
},
"dev":{
"dependsOn":["prepare"],
"cache":false,
"persistent":true
}
}
}
```
### Transit Nodes for Parallel Tasks with Cache Invalidation
Some tasks can run in parallel (don't need built output from dependencies) but must invalidate cache when dependency source code changes.
**The problem with `dependsOn: ["^taskname"]`:**
- Forces sequential execution (slow)
**The problem with `dependsOn: []` (no dependencies):**
- Allows parallel execution (fast)
- But cache is INCORRECT - changing dependency source won't invalidate cache
**Transit Nodes solve both:**
```json
{
"tasks":{
"transit":{"dependsOn":["^transit"]},
"my-task":{"dependsOn":["transit"]}
}
}
```
The `transit` task creates dependency relationships without matching any actual script, so tasks run in parallel with correct cache invalidation.
**How to identify tasks that need this pattern:** Look for tasks that read source files from dependencies but don't need their build outputs.
### With Environment Variables
```json
{
"globalEnv":["NODE_ENV"],
"globalDependencies":[".env"],
"tasks":{
"build":{
"dependsOn":["^build"],
"outputs":["dist/**"],
"env":["API_URL","DATABASE_URL"]
}
}
}
```
With `futureFlags.globalConfiguration`, the same config moves global settings under `global` — and `.env` becomes a per-task input instead of a global hash input:
description:Load Turborepo skill for creating workflows, tasks, and pipelines in monorepos. Use when users ask to "create a workflow", "make a task", "generate a pipeline", or set up build orchestration.
---
Load the Turborepo skill and help with monorepo task orchestration: creating workflows, configuring tasks, setting up pipelines, and optimizing builds.
## Workflow
### Step 1: Load turborepo skill
```
skill({ name: 'turborepo' })
```
### Step 2: Identify task type from user request
Analyze $ARGUMENTS to determine:
- **Topic**: configuration, caching, filtering, environment, CI, or CLI
- **Task type**: new setup, debugging, optimization, or implementation
Use decision trees in SKILL.md to select the relevant reference files.
### Step 3: Read relevant reference files
Based on task type, read from `references/<topic>/`:
For internal packages, prefer `tsc` over bundlers. Bundlers can mangle code before it reaches your app's bundler, causing hard-to-debug issues.
### Enable Go-to-Definition
For Compiled Packages, enable declaration maps:
```json
// tsconfig.json
{
"compilerOptions":{
"declaration":true,
"declarationMap":true
}
}
```
This creates `.d.ts` and `.d.ts.map` files for IDE navigation.
### No Root tsconfig.json Needed
Each package should have its own `tsconfig.json`. A root one causes all tasks to miss cache when changed. Only use root `tsconfig.json` for non-package scripts.
### Avoid TypeScript Project References
They add complexity and another caching layer. Turborepo handles dependencies better.
When `futureFlags.globalConfiguration` is enabled, `global.inputs` files are **not** part of the global hash. Instead, they are prepended to every task's `inputs` and folded into the **task hash**. This is a fundamental change from `globalDependencies`.
**With `globalDependencies` (default):**
```
task cache key = hash(global hash, task hash)
↑ includes globalDependencies file hashes
```
Changing a `globalDependencies` file invalidates **every** task, regardless of task-level `inputs`. There is no way for a task to opt out.
In this example, changing `tsconfig.json` invalidates `build` (it's in the task's inputs) but **not**`lint` (which explicitly excludes it). With `globalDependencies`, both would have been invalidated.
## What Gets Cached
1.**File outputs** - files/directories specified in `outputs`
2.**Task logs** - stdout/stderr for replay on cache hit
```json
{
"tasks":{
"build":{
"outputs":["dist/**",".next/**"]
}
}
}
```
## Local Cache Location
```
.turbo/cache/
├── <hash1>.tar.zst # compressed outputs
├── <hash2>.tar.zst
└── ...
```
Add `.turbo` to `.gitignore`.
## Cache Restoration
On cache hit, Turborepo:
1. Extracts archived outputs to their original locations
2. Replays the logged stdout/stderr
3. Reports the task as cached (shows `FULL TURBO` in output)
## Example Flow
```bash
# First run - executes build, caches result
turbo build
# → packages/ui: cache miss, executing...
# → packages/web: cache miss, executing...
# Second run - same inputs, restores from cache
turbo build
# → packages/ui: cache hit, replaying output
# → packages/web: cache hit, replaying output
# → FULL TURBO
```
## Key Points
- Cache is content-addressed (based on input hash, not timestamps)
- Empty `outputs` array means task runs but nothing is cached
- Tasks without `outputs` key cache nothing (use `"outputs": []` to be explicit)
Shows cache status for each task without running them.
### `--force`
Skip reading cache, re-execute all tasks:
```bash
turbo build --force
```
Useful to verify tasks actually work (not just cached results).
## Unexpected Cache Misses
**Symptom:** Task runs when you expected a cache hit.
### Environment Variable Changed
Check if an env var in the `env` key changed:
```json
{
"tasks":{
"build":{
"env":["API_URL","NODE_ENV"]
}
}
}
```
Different `API_URL` between runs = cache miss.
### .env File Changed
`.env` files aren't tracked by default. Add to `inputs`:
```json
{
"tasks":{
"build":{
"inputs":["$TURBO_DEFAULT$",".env",".env.local"]
}
}
}
```
Or use `globalDependencies` for repo-wide env files:
```json
{
"globalDependencies":[".env"]
}
```
With `futureFlags.globalConfiguration`, use `global.inputs` instead. The key difference: `global.inputs` files are folded into each task's hash individually (not the global hash), so tasks can exclude specific files with negation globs.
```json
{
"futureFlags":{"globalConfiguration":true},
"global":{
"inputs":[".env"]
}
}
```
### Lockfile Changed
Installing/updating packages changes the global hash.
### Source Files Changed
Any file in the package (or in `inputs`) triggers a miss.
### turbo.json Changed
Config changes invalidate the global hash.
## Incorrect Cache Hits
**Symptom:** Cached output is stale/wrong.
### Missing Environment Variable
Task uses an env var not listed in `env`:
```javascript
// build.js
constapiUrl=process.env.API_URL;// not tracked!
```
Fix: add to task config:
```json
{
"tasks":{
"build":{
"env":["API_URL"]
}
}
}
```
### Missing File in Inputs
Task reads a file outside default inputs:
```json
{
"tasks":{
"build":{
"inputs":[
"$TURBO_DEFAULT$",
"../../shared-config.json"// file outside package
]
}
}
}
```
## Useful Flags
```bash
# Only show output for cache misses
turbo build --output-logs=new-only
# Show output for everything (debugging)
turbo build --output-logs=full
# See why tasks are running
turbo build --verbosity=2
```
## Debugging with `globalConfiguration` Enabled
When `futureFlags.globalConfiguration` is on, `global.inputs` files appear in per-task hash inputs (not the global hash). If you're getting unexpected cache misses:
1. Check `--summarize` output — global input files will show up in the **task inputs** section, not the global hash section
2. Verify tasks aren't accidentally excluding global inputs via negation globs in `inputs`
3. Remember that toggling the `globalConfiguration` flag itself invalidates all caches (the flag value is part of the global hash)
If you're getting unexpected cache **hits** after changing a global input file, the task may be excluding that file with a negation glob. Check the task's `inputs` for `!$TURBO_ROOT$/...` patterns.
## Quick Checklist
Cache miss when expected hit:
1. Run with `--summarize`, compare with previous run
General principles for running Turborepo in continuous integration environments.
## Core Principles
### Always Use `turbo run` in CI
**Never use the `turbo <tasks>` shorthand in CI or scripts.** Always use `turbo run`:
```bash
# CORRECT - Always use in CI, package.json, scripts
turbo run build test lint
# WRONG - Shorthand is only for one-off terminal commands
turbo build test lint
```
The shorthand `turbo <tasks>` is only for one-off invocations typed directly in terminal by humans or agents. Anywhere the command is written into code (CI, package.json, scripts), use `turbo run`.
### Enable Remote Caching
Remote caching dramatically speeds up CI by sharing cached artifacts across runs.
Required environment variables:
```bash
TURBO_TOKEN=your_vercel_token
TURBO_TEAM=your_team_slug
```
### Use --affected for PR Builds
The `--affected` flag only runs tasks for packages changed since the base branch:
```bash
turbo run build test --affected
```
This requires Git history to compute what changed.
## Git History Requirements
### Fetch Depth
`--affected` needs access to the merge base. Shallow clones break this.
```yaml
# GitHub Actions
- uses:actions/checkout@v4
with:
fetch-depth:2# Minimum for --affected
# Use 0 for full history if merge base is far
```
### Why Shallow Clones Break --affected
Turborepo compares the current HEAD to the merge base with `main`. If that commit isn't fetched, `--affected` falls back to running everything.
**Requires git history** - shallow clones may fall back to running all tasks.
## Execution Control
### `--dry` / `--dry=json`
Preview what would run without executing.
```bash
turbo build --dry # human-readable
turbo build --dry=json # machine-readable
```
### `--force`
Ignore all cached artifacts, re-run everything.
```bash
turbo build --force
```
### `--concurrency`
Limit parallel task execution.
```bash
turbo build --concurrency=4# max 4 tasks
turbo build --concurrency=50% # 50% of CPU cores
```
### `--continue`
Keep running other tasks when one fails.
```bash
turbo build test --continue
```
### `--only`
Run only the specified task, skip its dependencies.
```bash
turbo build --only # skip running dependsOn tasks
```
### `--parallel` (Discouraged)
Ignores task graph dependencies, runs all tasks simultaneously. **Avoid using this flag**—if tasks need to run in parallel, configure `dependsOn` correctly instead. Using `--parallel` bypasses Turborepo's dependency graph, which can cause race conditions and incorrect builds.
Configuration reference for Turborepo. Full docs: https://turborepo.dev/docs/reference/configuration
## File Location
Root `turbo.json` lives at repo root, sibling to root `package.json`:
```
my-monorepo/
├── turbo.json # Root configuration
├── package.json
└── packages/
└── web/
├── turbo.json # Package Configuration (optional)
└── package.json
```
## Always Prefer Package Tasks Over Root Tasks
**Always use package tasks. Only use Root Tasks if you cannot succeed with package tasks.**
Package tasks enable parallelization, individual caching, and filtering. Define scripts in each package's `package.json`:
```json
// packages/web/package.json
{
"scripts":{
"build":"next build",
"lint":"eslint .",
"test":"vitest",
"typecheck":"tsc --noEmit"
}
}
// packages/api/package.json
{
"scripts":{
"build":"tsc",
"lint":"eslint .",
"test":"vitest",
"typecheck":"tsc --noEmit"
}
}
```
```json
// Root package.json - delegates to turbo
{
"scripts":{
"build":"turbo run build",
"lint":"turbo run lint",
"test":"turbo run test",
"typecheck":"turbo run typecheck"
}
}
```
When you run `turbo run lint`, Turborepo finds all packages with a `lint` script and runs them **in parallel**.
**Root Tasks are a fallback**, not the default. Only use them for tasks that truly cannot run per-package (e.g., repo-level CI scripts, workspace-wide config generation).
```json
// AVOID: Task logic in root defeats parallelization
**Deprecated**: The daemon is no longer used for `turbo run` and this option will be removed in version 3.0. The daemon is still used by `turbo watch` and the Turborepo LSP.
## envMode
How unspecified env vars are handled. Default: `"strict"`.
```json
{
"envMode":"strict"// Only specified vars available
- Cache hit: `cache hit, replaying logs (no errors) <hash>`
### `longerSignatureKey`
Enforce a minimum key length of 32 bytes for `TURBO_REMOTE_CACHE_SIGNATURE_KEY` when `remoteCache.signature` is enabled. Short keys weaken HMAC-SHA256 signatures. Fails the run immediately if the key is too short.
### `globalConfiguration`
Moves global configuration keys under a top-level `global` key for clarity and changes how `global.inputs` (formerly `globalDependencies`) affects task hashing.
When enabled:
- Global config keys move under `global` with cleaner names
-`global.inputs` files are **prepended to every task's inputs** instead of being folded into the global hash — tasks can opt out of specific global inputs using negation globs
| `ui`, `envMode`, `cacheDir`, `daemon`, `concurrency`, `noUpdateNotifier`, `dangerouslyDisablePackageManagerCheck`, `remoteCache` | Same names under `global` |
**Behavior change for `global.inputs`:**
With `globalDependencies` (old): files are hashed into the **global hash**, which is embedded in every task's cache key. Changing any of these files invalidates all tasks — there is no opt-out.
With `global.inputs` (new): files are treated as **implicit task inputs** prepended to each task's `inputs` globs. This means:
- Tasks can exclude specific global files: `"inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/tsconfig.json"]`
- The global hash no longer includes these file hashes (it still includes lockfile, engines, global env, etc.)
- Tasks with no explicit `inputs` still hash all package files plus the global inputs
See the [gotchas doc](./gotchas.md) for guidance on using `$TURBO_DEFAULT$` with `global.inputs`.
## noUpdateNotifier
Disable update notifications when new turbo versions are available.
```json
{
"noUpdateNotifier":true
}
```
## dangerouslyDisablePackageManagerCheck
Bypass the `packageManager` field requirement. Use for incremental migration.
```json
{
"dangerouslyDisablePackageManagerCheck":true
}
```
**Warning**: Unstable lockfiles can cause unpredictable behavior.
## Git Worktree Cache Sharing
When working in Git worktrees, Turborepo automatically shares local cache between the main worktree and linked worktrees.
**How it works:**
- Detects worktree configuration
- Redirects cache to main worktree's `.turbo/cache`
- Works alongside Remote Cache
**Benefits:**
- Cache hits across branches
- Reduced disk usage
- Faster branch switching
**Disabled by**: Setting explicit `cacheDir` in turbo.json.
Root `package.json` scripts for turbo tasks MUST use `turbo run`, not direct commands.
```json
// WRONG - bypasses turbo, no parallelization or caching
{
"scripts":{
"build":"bun build",
"dev":"bun dev"
}
}
// CORRECT - delegates to turbo
{
"scripts":{
"build":"turbo run build",
"dev":"turbo run dev"
}
}
```
**Why this matters:** Running `bun build` or `npm run build` at root bypasses Turborepo entirely - no parallelization, no caching, no dependency graph awareness.
## #2 Using `&&` to Chain Turbo Tasks
Don't use `&&` to chain tasks that turbo should orchestrate.
```json
// WRONG - changeset:publish chains turbo task with non-turbo command
// CORRECT - use turbo run, let turbo handle dependencies
{
"scripts":{
"changeset:publish":"turbo run build && changeset publish"
}
}
```
If the second command (`changeset publish`) depends on build outputs, the turbo task should run through turbo to get caching and parallelization benefits.
## #3 Overly Broad globalDependencies
`globalDependencies` affects hash for ALL tasks in ALL packages. Be specific.
```json
// WRONG - affects all hashes
{
"globalDependencies":["**/.env.*local"]
}
// CORRECT - move to specific tasks that need it
{
"globalDependencies":[".env"],
"tasks":{
"build":{
"inputs":["$TURBO_DEFAULT$",".env*"],
"outputs":["dist/**"]
}
}
}
```
**Why this matters:**`**/.env.*local` matches .env files in ALL packages, causing unnecessary cache invalidation. Instead:
- Use `globalDependencies` only for truly global files (root `.env`)
- Use task-level `inputs` for package-specific .env files with `$TURBO_DEFAULT$` to preserve default behavior
With `futureFlags.globalConfiguration`, this is less of a concern because `global.inputs` acts as implicit task inputs — tasks can opt out of specific files with negation globs. But keeping the list focused is still good practice.
## #4 Repetitive Task Configuration
Look for repeated configuration across tasks that can be collapsed.
```json
// WRONG - repetitive env and inputs across tasks
{
"tasks":{
"build":{
"env":["API_URL","DATABASE_URL"],
"inputs":["$TURBO_DEFAULT$",".env*"]
},
"test":{
"env":["API_URL","DATABASE_URL"],
"inputs":["$TURBO_DEFAULT$",".env*"]
}
}
}
// BETTER - use globalEnv and globalDependencies
{
"globalEnv":["API_URL","DATABASE_URL"],
"globalDependencies":[".env*"],
"tasks":{
"build":{},
"test":{}
}
}
```
**When to use global vs task-level:**
-`globalEnv` / `globalDependencies` - affects ALL tasks, use for truly shared config
- Task-level `env` / `inputs` - use when only specific tasks need it
## #5 Using `../` to Traverse Out of Package in `inputs`
Don't use relative paths like `../` to reference files outside the package. Use `$TURBO_ROOT$` instead.
- Package tasks run in **parallel** across all packages
- Each package's output is cached **individually**
- You can **filter** to specific packages: `turbo run test --filter=web`
Root Tasks (`//#taskname`) defeat all these benefits. Only use them for tasks that truly cannot exist in any package (extremely rare).
## #7 Tasks That Need Parallel Execution + Cache Invalidation
Some tasks can run in parallel (don't need built output from dependencies) but must still invalidate cache when dependency source code changes. Using `dependsOn: ["^taskname"]` forces sequential execution. Using no dependencies breaks cache invalidation.
**Use Transit Nodes for these tasks:**
```json
// WRONG - forces sequential execution (SLOW)
"my-task":{
"dependsOn":["^my-task"]
}
// ALSO WRONG - no dependency awareness (INCORRECT CACHING)
"my-task":{}
// CORRECT - use Transit Nodes for parallel + correct caching
{
"tasks":{
"transit":{"dependsOn":["^transit"]},
"my-task":{"dependsOn":["transit"]}
}
}
```
**Why Transit Nodes work:**
-`transit` creates dependency relationships without matching any actual script
- Tasks that depend on `transit` gain dependency awareness
- Since `transit` completes instantly (no script), tasks run in parallel
- Cache correctly invalidates when dependency source code changes
**How to identify tasks that need this pattern:** Look for tasks that read source files from dependencies but don't need their build outputs.
## Missing outputs for File-Producing Tasks
**Before flagging missing `outputs`, check what the task actually produces:**
1. Read the package's script (e.g., `"build": "tsc"`, `"test": "vitest"`)
2. Determine if it writes files to disk or only outputs to stdout
3. Only flag if the task produces files that should be cached
```json
// WRONG - build produces files but they're not cached
"build":{
"dependsOn":["^build"]
}
// CORRECT - outputs are cached
"build":{
"dependsOn":["^build"],
"outputs":["dist/**"]
}
```
No `outputs` key is fine for stdout-only tasks. For file-producing tasks, missing `outputs` means Turbo has nothing to cache.
## Forgetting ^ in dependsOn
```json
// WRONG - looks for "build" in SAME package (infinite loop or missing)
"build":{
"dependsOn":["build"]
}
// CORRECT - runs dependencies' build first
"build":{
"dependsOn":["^build"]
}
```
The `^` means "in dependency packages", not "in this package".
## Missing persistent on Dev Tasks
```json
// WRONG - dependent tasks hang waiting for dev to "finish"
"dev":{
"cache":false
}
// CORRECT
"dev":{
"cache":false,
"persistent":true
}
```
## Package Config Missing extends
```json
// WRONG - packages/web/turbo.json
{
"tasks":{
"build":{"outputs":[".next/**"]}
}
}
// CORRECT
{
"extends":["//"],
"tasks":{
"build":{"outputs":[".next/**"]}
}
}
```
Without `"extends": ["//"]`, Package Configurations are invalid.
## Root Tasks Need Special Syntax
To run a task defined only in root `package.json`:
// WRONG - only watches test files, ignores source changes
"test":{
"inputs":["tests/**"]
}
// CORRECT - extends defaults, adds test files
"test":{
"inputs":["$TURBO_DEFAULT$","tests/**"]
}
```
Without `$TURBO_DEFAULT$`, you replace all default file watching.
## Excluding `global.inputs` Without `$TURBO_DEFAULT$`
When using `futureFlags.globalConfiguration`, `global.inputs` values are prepended to every task's inputs. If you want to exclude a global input from a specific task, you **must** include `$TURBO_DEFAULT$` to preserve default file hashing.
```json
// WRONG - task hashes NO files at all (global input cancelled, no defaults)
"build":{
"inputs":["!$TURBO_ROOT$/config.txt"]
}
// CORRECT - task hashes all package files, minus config.txt
Without `$TURBO_DEFAULT$`, the only inclusion glob comes from `global.inputs`, which the negation cancels out. The task ends up with no inclusions and no default file hashing, so it hashes nothing. Changes to source files won't cause cache misses.
## Caching Tasks with Side Effects
```json
// WRONG - deploy might be skipped on cache hit
"deploy":{
"dependsOn":["build"]
}
// CORRECT
"deploy":{
"dependsOn":["build"],
"cache":false
}
```
Always disable cache for deploy, publish, or mutation tasks.
| `$TURBO_DEFAULT$` | Include default inputs, then add/remove |
| `$TURBO_ROOT$/<path>` | Reference files from repo root |
```json
{
"tasks":{
"build":{
"inputs":[
"$TURBO_DEFAULT$",
"!README.md",
"$TURBO_ROOT$/tsconfig.base.json"
]
}
}
}
```
### Interaction with `global.inputs`
When `futureFlags.globalConfiguration` is enabled, files listed in `global.inputs` are prepended to every task's `inputs`. The combined list is then used to compute the task hash.
This is different from `globalDependencies`, where files were hashed into the **global** hash and could not be influenced by task-level `inputs`.
**With `globalDependencies` (old behavior):**
-`globalDependencies` files contribute to the global hash
- Task `inputs` only control which **package** files are hashed
- There is no way for a task to "opt out" of a `globalDependencies` file
**With `global.inputs` (new behavior):**
-`global.inputs` files are merged into each task's `inputs` globs
- Task `inputs` and `global.inputs` are combined, then the full list is hashed into the **task** hash
- Tasks can exclude specific global files with negation globs
### `globalPassThroughEnv` - Global Runtime Variables
Same as `passThroughEnv` but for all tasks.
```json
{
"globalPassThroughEnv":["GITHUB_TOKEN"]
}
```
## Wildcards and Negation
### Wildcards
Match multiple variables with `*`:
```json
{
"env":["MY_API_*","FEATURE_FLAG_*"]
}
```
This matches `MY_API_URL`, `MY_API_KEY`, `FEATURE_FLAG_DARK_MODE`, etc.
### Negation
Exclude variables (useful with framework inference):
```json
{
"env":["!NEXT_PUBLIC_ANALYTICS_ID"]
}
```
## With `futureFlags.globalConfiguration`
When the `globalConfiguration` future flag is enabled, global environment keys move under the `global` key with cleaner names:
| Old (top-level) | New (`global.`) |
| ---------------------- | ---------------- |
| `globalEnv` | `env` |
| `globalPassThroughEnv` | `passThroughEnv` |
`global.env` and `global.passThroughEnv` behave identically to their top-level counterparts — they affect the global hash and all tasks, respectively. The rename is purely organizational.
- Hashes DATABASE*URL and NEXT_PUBLIC*\* vars (except analytics)
- Passes through SENTRY_AUTH_TOKEN without hashing
- Includes all .env file variants in the hash
- Makes CI tokens available globally
### With `futureFlags.globalConfiguration`
The same config using the `global` key. The `.env` files move to `global.inputs`, which means they get folded into each task's hash individually rather than the global hash. This lets tasks exclude specific `.env` files if needed.
This wouldn't have been possible with `globalDependencies`, where `.env.production` would be baked into the global hash and affect every task unconditionally.
description:Use this agent when a feature branch is complete and ready to merge to main, and you need to create a changelog entry documenting the new feature or changes. This agent should be invoked proactively after significant feature work is completed and before merging.\n\nExamples:\n\n<example>\nContext:User has just completed implementing a new tracing visualization feature and the code has been reviewed.\nuser:"I've finished the trace timeline view feature. Can you help me prepare this for merge?"\nassistant:"Let me use the changelog-writer agent to create a changelog entry for this feature."\n<commentary>\nThe feature is complete and ready for merge, so we should use the changelog-writer agent to document it in the changelog.\n</commentary>\n</example>\n\n<example>\nContext:User mentions they're done with a feature implementation.\nuser:"The prompt versioning feature is done and tested. What's next?"\nassistant:"Great! Let me use the changelog-writer agent to create a changelog entry documenting this new feature before we merge."\n<commentary>\nSince the feature is complete, proactively use the changelog-writer agent to create documentation.\n</commentary>\n</example>\n\n<example>\nContext:User explicitly requests changelog creation.\nuser:"Can you create a changelog post for the new dataset export functionality?"\nassistant:"I'll use the changelog-writer agent to analyze the changes and create an appropriate changelog entry."\n<commentary>\nDirect request to create changelog, use the changelog-writer agent.\n</commentary>\n</example>
model:inherit
color:pink
---
You are an expert technical writer specializing in creating clear, user-focused changelog entries for developer tools and SaaS platforms. Your role is to document completed features in a way that helps users understand what's new, why it matters, and how to use it.
## Your Process
### Step 1: Understand the Changes
1. Extract the Linear issue number from the current branch name (format: lfe-XXXX)
2. Use the Linear MCP to fetch the issue details for additional context about the feature's purpose and requirements
3. Compare the current branch to main using git diff to understand the scope of changes at a high level
4. Identify the core feature or improvement that was implemented
5. Determine which parts of the codebase were affected (frontend, backend, API, database, etc.)
### Step 2: Study Recent Changelog Patterns
1. Read 3-5 of the most recent changelog posts in `../langfuse-docs/pages/changelog`
2. Analyze their structure, tone, and formatting conventions
3. Note how they:
- Title features (concise, benefit-focused)
- Explain the "why" (user problems solved)
- Describe the "what" (feature capabilities)
- Link to relevant documentation
- Use images/screenshots
- Format code examples or technical details
### Step 3: Identify Documentation Links
1. Check if there is relevant documentation in `../langfuse-docs/pages` that relates to this feature
2. If the feature is new, note that documentation may need to be created
3. If the feature extends existing functionality, identify which docs pages should be referenced
### Step 4: Draft the Changelog Entry
Create a changelog post that includes:
**Required Elements:**
- **Title**: Clear, benefit-focused headline (not just the feature name)
- **Date**: Use the current date in the format used by existing changelogs
- **Summary**: 1-2 sentences explaining what changed and why it matters to users
- **Description**: Detailed explanation of the feature, its capabilities, and use cases
- **Documentation Links**: References to relevant docs pages (if applicable)
**Style Guidelines:**
- Write in second person ("you can now...")
- Focus on user benefits, not implementation details
- Be concise but complete
- Use active voice
- Include technical details only when they help users understand the feature
- Match the tone and style of recent changelog entries
**Formatting:**
- Follow the exact file structure and frontmatter format of existing changelog posts
- Use appropriate markdown formatting (headings, lists, code blocks, links)
- Ensure proper spacing and readability
### Step 5: Assess Visual Needs
After drafting the changelog, explicitly tell the user:
- Whether a screenshot or image would enhance understanding of this feature
- What specific aspect should be captured in the screenshot (if applicable)
- Where in the changelog the image should be placed
## Quality Standards
**Before presenting your changelog:**
- Verify it follows the structure and style of recent entries
- Ensure all links are correctly formatted
- Check that technical terms match those used in the codebase and docs
- Confirm the feature description is accurate based on the code changes
- Validate that the user benefit is clear and compelling
## Output Format
Present your work in this order:
1. Brief summary of what you learned from the branch comparison and Linear issue
2. The complete changelog post content (ready to be saved as a new file)
3. Recommendation on whether to add an image/screenshot and what it should show
4. List of any documentation pages that should be referenced or created
## Important Notes
- The changelog lives in `../langfuse-docs/pages/changelog`
- Always check the Linear issue via the branch name (lfe-XXXX format) for context
- Compare against main branch to understand the full scope of changes
- Study recent changelogs before writing to maintain consistency
- Focus on user value, not technical implementation details
- Be thorough in your analysis before drafting
- If you're unsure about any aspect of the feature, ask clarifying questions before proceeding
console.error("Error in skill-activation-prompt hook:",err);
process.exit(1);
}
}
main().catch((err)=>{
console.error("Uncaught error:",err);
process.exit(1);
});
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.