Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
195 commits
Select commit Hold shift + click to select a range
bb10445
fix(mcp-analytics): count rejected calls against their target tool
vdekrijger Sep 28, 2026
9140a77
fix(mcp): preserve query failures and record service reliability
vdekrijger Sep 28, 2026
119a687
fix(mcp): make non-retryable recovery advice explicit
vdekrijger Sep 28, 2026
5782d0f
fix(mcp): declare the access-control facade dependency
vdekrijger Sep 28, 2026
8ee6ce5
fix(mcp-analytics): align call views and notification attribution
vdekrijger Sep 28, 2026
f0443ea
Merge remote-tracking branch 'origin/master' into codex/mcp-rejected-…
vdekrijger Sep 28, 2026
f49afc4
Merge branch 'codex/mcp-rejected-call-attribution' into codex/mcp-que…
vdekrijger Sep 28, 2026
c4dc370
chore(mcp-analytics): remove dispatcher details from user docs
vdekrijger Sep 29, 2026
26cba8e
Merge remote-tracking branch 'origin/master' into codex/mcp-rejected-…
vdekrijger Sep 29, 2026
4eec0fb
Merge branch 'codex/mcp-rejected-call-attribution' into codex/mcp-que…
vdekrijger Sep 29, 2026
6be58cf
Merge remote-tracking branch 'origin/master' into codex/mcp-rejected-…
vdekrijger Sep 29, 2026
066682e
Merge branch 'codex/mcp-rejected-call-attribution' into codex/mcp-que…
vdekrijger Sep 29, 2026
62ce4d8
fix(mcp): preserve recovery for query budget failures
vdekrijger Sep 29, 2026
db38ae0
chore(mcp): remove reliability documentation from PR
vdekrijger Sep 29, 2026
a274d83
Merge remote-tracking branch 'origin/master' into codex/mcp-rejected-…
vdekrijger Sep 29, 2026
9f4ecb4
Merge branch 'codex/mcp-rejected-call-attribution' into codex/mcp-que…
vdekrijger Sep 29, 2026
7d5eda9
chore(mcp): separate error classification from exception traversal
vdekrijger Sep 29, 2026
8e5ffd5
Merge remote-tracking branch 'origin/master' into codex/mcp-rejected-…
vdekrijger Sep 29, 2026
e98820e
fix(mcp-analytics): align scout attribution and simplify alerts
vdekrijger Sep 29, 2026
593f643
Merge branch 'codex/mcp-rejected-call-attribution' into codex/mcp-que…
vdekrijger Sep 29, 2026
301221d
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Sep 29, 2026
ecd31b0
fix(mcp): reduce error classifier complexity
vdekrijger Sep 29, 2026
d621255
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Sep 29, 2026
bc903c7
fix(mcp): reuse existing tool-call reliability metrics
vdekrijger Sep 29, 2026
5048c38
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Sep 29, 2026
0f108d6
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Sep 29, 2026
d4701bc
refactor(mcp): define error codes and messages together
vdekrijger Sep 29, 2026
17d3699
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Sep 29, 2026
306f6d3
test(mcp): compare complete error messages
vdekrijger Sep 29, 2026
d4e564f
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Sep 29, 2026
57f7e96
refactor(mcp): leave error messages with their tools
vdekrijger Sep 29, 2026
4b0aa8d
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Sep 29, 2026
a5087c6
fix(mcp): normalize query errors and sanitize failure analytics
vdekrijger Sep 29, 2026
6c18f0e
fix(mcp): retain recovery advice for async query timeouts
vdekrijger Sep 29, 2026
88928c1
fix(mcp): name the correct --json placement for exec call
posthog[bot] Sep 30, 2026
784eb01
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Sep 30, 2026
bd2b041
fix(mcp): reuse tool errors for query recovery
vdekrijger Sep 30, 2026
58e909e
chore(mcp): complete query recovery type annotations
vdekrijger Sep 30, 2026
c9c6623
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Sep 30, 2026
91602fe
fix(mcp): distinguish query memory limits in metrics
vdekrijger Sep 30, 2026
6309aea
chore: merge master into mcp query recovery
vdekrijger Sep 30, 2026
a0bb673
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Sep 30, 2026
04b9489
fix(mcp): preserve query recovery across error paths
vdekrijger Sep 30, 2026
886c898
Rebase warehouse scheduler shadow onto master
Gilbert09 Sep 30, 2026
e821e45
fix(data-warehouse): harden scheduler shadow accounting
Gilbert09 Sep 30, 2026
44df8ae
refactor(data-warehouse): key the queue scheduler tables by kind and …
Gilbert09 Sep 30, 2026
0aa9278
fix(mcp): keep warehouse failures out of input errors
vdekrijger Oct 1, 2026
5977136
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Oct 1, 2026
47a7c34
fix(mcp): preserve internal attribution for untyped results
vdekrijger Oct 1, 2026
cf66eea
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Oct 1, 2026
fd4cbd2
fix(mcp): distinguish permission and internal query failures
vdekrijger Oct 1, 2026
7834c59
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Oct 1, 2026
d48c415
fix(mcp): preserve retry advice for untyped query errors
vdekrijger Oct 1, 2026
251d8ea
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Oct 1, 2026
47224a4
feat(data-warehouse): let a new non-resumable import leave a stopping…
Gilbert09 Oct 1, 2026
89d3457
fix(mcp): preserve backend error categories in cli analytics
vdekrijger Oct 1, 2026
112dc70
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Oct 1, 2026
a32363e
feat(error-tracking): include stack trace in created external issues
ablaszkiewicz Oct 1, 2026
8bca3d7
fix(mcp): classify rejected connection ids as validation
vdekrijger Oct 1, 2026
cd94f62
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Oct 1, 2026
dc41a38
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Oct 1, 2026
03511b3
fix(mcp): classify raw sql guards before async dispatch
vdekrijger Oct 1, 2026
ea2bea2
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Oct 1, 2026
0fa2d78
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Oct 1, 2026
149d4b8
fix(ci): hold single-language JS workflows to the lanes they test
gantoine Oct 1, 2026
aef7ba6
chore(ci): fail prs that drop live storybook baselines
gantoine Oct 1, 2026
6838f39
fix(ci): put clickhouse-udfs on the python merge queue lanes
gantoine Oct 1, 2026
98455ca
chore(ci): exempt rendered stories and print baselines to restore
gantoine Oct 1, 2026
6477a47
feat(experiments): scheduled recalculation selection logic
rodrigoi Sep 29, 2026
8af95c5
fix(experiments): pin the clock in scheduled recalculation logic tests
rodrigoi Sep 29, 2026
0d19c56
fix(experiments): defer the recalculation hour import to break a cycle
rodrigoi Sep 29, 2026
422ff89
test(experiments): cover the exposure count and its multiple-variant …
rodrigoi Sep 29, 2026
395b101
fix(experiments): serialize the holdout before the exposure query
rodrigoi Sep 29, 2026
105c919
feat(experiments): select scheduled candidates from the current hour
rodrigoi Oct 1, 2026
bed50f3
feat(experiments): honor every configured recalculation time
rodrigoi Oct 2, 2026
073b49a
feat(experiments): scheduled recalculation workflow and activities
rodrigoi Sep 29, 2026
cfb6054
fix(experiments): pin the clock and expose worker registration lists
rodrigoi Sep 29, 2026
4095984
fix(experiments): recheck running state before dispatching a schedule…
rodrigoi Sep 29, 2026
e0b0b65
chore(experiments): annotate the scheduled recalculation test helpers
rodrigoi Sep 29, 2026
5ac3e91
fix(experiments): adopt the new start_metrics_recalculation_workflow …
rodrigoi Oct 1, 2026
b1ba535
chore(experiments): re-run ci after a cancelled run
rodrigoi Oct 1, 2026
78ba98b
fix(experiments): defer recalculation imports to break an import cycle
rodrigoi Oct 1, 2026
c51cac9
feat(experiments): drop the hour input from the recalculation coordin…
rodrigoi Oct 1, 2026
3bfdcd7
feat(experiments): record scheduled runs with the scheduled trigger
rodrigoi Oct 2, 2026
8c9cef6
chore(experiments): prefix the scheduled recalculation worker groups
rodrigoi Oct 2, 2026
4b5ac8a
chore(experiments): re-run ci after a cancelled run
rodrigoi Oct 1, 2026
9fb88e0
chore(experiments): re-run ci after a cancelled run
rodrigoi Oct 1, 2026
ff7234d
feat(experiments): schedule experiment recalculations hourly
rodrigoi Sep 29, 2026
206e616
test(experiments): assert the full schedule contract per hour
rodrigoi Sep 29, 2026
c7c0a70
fix(experiments): expose the scheduled recalculation creator to core
rodrigoi Sep 29, 2026
f75feff
feat(experiments): replace the 24 schedules with one hourly schedule
rodrigoi Oct 1, 2026
59815f5
chore(experiments): document the shared recalculation hour filter
rodrigoi Oct 2, 2026
7626970
chore(experiments): drop the unused legacy schedule cleanup
rodrigoi Oct 2, 2026
2466c7a
fix(inbox): keep the pr limit modal open when billing rejects a save
skoob13 Oct 2, 2026
05f959b
chore(inbox): regenerate kea types for the pr limit logic
skoob13 Oct 2, 2026
4d75726
fix(canvas): fill the page in canvas full page view without browser f…
puemos Oct 2, 2026
0caed85
fix(mcp): classify only application query errors
vdekrijger Oct 2, 2026
51d47a1
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Oct 2, 2026
4a13db6
feat(desktop): let agents cite posthog objects as markdown links
puemos Oct 2, 2026
5cd4a7e
refactor(desktop): simplify object link parsing and shared hooks
puemos Oct 2, 2026
db658f5
chore(desktop): check the link prompt in the pi enrichment e2e test
puemos Oct 2, 2026
f8261c4
chore(desktop): restore the auth store after link tests and stories
puemos Oct 2, 2026
d7c7721
fix(tasks): keep slack output unchanged and cut notifications outside…
puemos Oct 2, 2026
98538f4
fix(frontend): show the sql block only for links on the app's own origin
puemos Oct 2, 2026
e235d2b
fix(desktop): skip indented code blocks when extracting object refere…
puemos Oct 2, 2026
7d5bfbc
Merge remote-tracking branch 'origin/master' into codex/mcp-query-err…
vdekrijger Oct 2, 2026
0d83ab6
chore(visual): update storybook baselines
posthog[bot] Oct 2, 2026
fe50ddf
fix(replay): stamp source on replay and vision events reached from MC…
fasyy612 Oct 2, 2026
4415e7f
chore: update OpenAPI generated types
tests-posthog[bot] Oct 2, 2026
82bf191
feat(error-tracking): move stack trace into an include checkbox
ablaszkiewicz Oct 2, 2026
bbb3062
feat(data-warehouse): support import syncs for the trino source
Gilbert09 Oct 2, 2026
8be003f
fix(error-tracking): harden stack traces in external issues
ablaszkiewicz Oct 2, 2026
7147bfd
fix(charts): infer date axis interval from the smallest gap
posthog[bot] Oct 2, 2026
3f79d0f
feat(data-warehouse): release the trino source
Gilbert09 Oct 2, 2026
c17cd21
fix(error-tracking): drop frame code variables for listed teams in cy…
ablaszkiewicz Oct 2, 2026
ff6ec68
fix(data-warehouse): keep google sheets schemas across worksheet renames
Gilbert09 Oct 2, 2026
db8fee0
fix(error-tracking): reject invalid team ids for the code variables drop
ablaszkiewicz Oct 2, 2026
274753d
chore(error-tracking): trim comments on the code variables drop
ablaszkiewicz Oct 2, 2026
ca3acca
fix(error-tracking): read the event at submit and preview the sent trace
claude Oct 2, 2026
75e5c1a
feat(signals): train the inbox ranking open head on every report
posthog[bot] Oct 2, 2026
f0f83b7
fix(files): support dragging into starred folders
mariusandra Oct 2, 2026
5dd0dd5
chore: merge master into starred drag-and-drop fix
mariusandra Oct 2, 2026
41339ba
feat(ai-research): label refused config fetches with their http status
robbie-c Oct 2, 2026
d77e557
fix(endpoints): serve ineligible materializations inline
sakce Oct 2, 2026
979264d
chore(data-warehouse): drop keyset compatibility path
Gilbert09 Oct 2, 2026
5d44f30
Merge remote-tracking branch 'origin/master' into fix/trunk-lanes-sin…
gantoine Oct 2, 2026
74af400
feat(tasks): block desktop access for users who join after a signup c…
k11kirky Oct 2, 2026
2947928
Merge remote-tracking branch 'origin/master' into fix/trunk-lanes-cli…
gantoine Oct 2, 2026
903aad5
fix(data-warehouse): preserve worksheet identity during discovery
Gilbert09 Oct 2, 2026
5cd7afb
feat(tasks): move the default desktop signup cutoff to 2026-10-01
k11kirky Oct 2, 2026
ed96ae6
fix(data-warehouse): justify SQLSource tuple protocol returns
Gilbert09 Oct 2, 2026
d67bb00
add test cases and better assert
fasyy612 Oct 2, 2026
3c083dd
chore(endpoints): annotate parameterized serving test
sakce Oct 2, 2026
b6c3b5c
feat(tasks): put the desktop signup gate behind a kill switch flag
k11kirky Oct 2, 2026
79304ff
Merge branch 'master' into fix/stamp-replay-source
fasyy612 Oct 2, 2026
e17aafb
chore(ci): read commented snapshot keys and retry baseline api reads
gantoine Oct 2, 2026
c5f5a8d
fix(data-warehouse): preserve worksheet identity during discovery
Gilbert09 Oct 2, 2026
41d4bee
fix(data-warehouse): preserve worksheet identity during discovery
Gilbert09 Oct 2, 2026
1ba5bf2
fix(endpoints): drive the ineligible serving test from HogQL
sakce Oct 2, 2026
9cf3073
feat(warehouse): add helicone properties and eval_scores tables
Gilbert09 Oct 2, 2026
f124b71
fix(files): distinguish starring from folder drops
mariusandra Oct 2, 2026
b3eb98c
chore(ci): post hogbox preview as a ci report section
gantoine Oct 2, 2026
187fb3a
chore: update starred drag-and-drop branch from master
mariusandra Oct 2, 2026
8dae7fd
feat(data-warehouse): add hellobaton project users, phases and custom…
Gilbert09 Oct 2, 2026
ad49221
feat(data-warehouse): add gridly projects, databases, grids and views…
Gilbert09 Oct 2, 2026
5c2aa96
feat(warehouse-sources): add gorgias events, voice call events and re…
Gilbert09 Oct 2, 2026
967f5ba
Merge master and resolve SQL tool test imports
vdekrijger Oct 2, 2026
664c409
feat(data-warehouse): add gong activity, library, flows, and call out…
Gilbert09 Oct 2, 2026
151004e
fix(warehouse): allow scalar Helicone response rows
Gilbert09 Oct 2, 2026
db91b86
chore(ci): cover conditional section update on teardown
gantoine Oct 2, 2026
4ef37fa
chore(warehouse): point helicone properties docs at request_properties
Gilbert09 Oct 2, 2026
4e7361f
chore(data-warehouse): scope hellobaton custom field values gap note …
Gilbert09 Oct 2, 2026
953efbf
fix(data-warehouse): satisfy Gridly type checks
Gilbert09 Oct 2, 2026
b31246f
fix(data-warehouse): satisfy Gridly review checks
Gilbert09 Oct 2, 2026
a39a9d0
chore(data-warehouse): resolve master conflicts
Gilbert09 Oct 2, 2026
62881e2
Merging 41339bad6979fd960ec8807462be685af3bc376b into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
36b44cd
Merging 979264dce8d0c4eefa7b18b1ba81ccf014979783 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
1b18150
Merging 7147bfd9c4fcdc5dc426cec120e980c8a6b5fc44 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
3d9cea2
Merging 7626970b3ba4745b4d1a8c7dd99f571c87b219a6 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
ad1bd9e
Merging 05f959b78897d5b45743e850ae4eca8cfdf0e14e into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
55c2ed4
Merging 75e5c1af5707c9ee29e67ff7c569588505d37c12 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
bfb94b0
Merging b6c3b5c204a42471ff57b4aef2982945ef78432a into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
77203c0
Merging 62881e2bbbb7200eaffaeca47f80b334d566284e into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
bc93cb4
Merging bfb94b0d026541c1797c4d58d3f9c6a401cdccf7 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
9f0d901
Merging ad1bd9ee1e3ca52a1b585c7eca935818c6772625 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
7dd3348
Merging 274753dd33a94ca5525b53de70f87984be2d8b84 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
698af1c
Merging e17aafb594e4ade0a81c320ce76f2d5c5cbd8128 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
c459f7a
Merging 44df8aeab0a4f76c39d57dfbd936d88c62cba5d3 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
74b85cf
Merging ca3acca6d4964b91703a573f5326af0280a03427 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
cd0aa0c
Merging 74b85cf60aac362111d0e5339ac04dd782e46043 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
a1c986a
Merging 41d4bee5ee9534c7a70a609b99012e6d8c71532b into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
c8b91db
Merging 5c2aa967213fe4289b1bbdfc22192625584c3d54 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
c50aa02
Merging 47224a4ee7d7ab3154ed377e4ac0ead6ca0671da into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
20ec305
Merging ed96ae6d456a754ac0ff252f519b957ef0a3c822 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
94f7fda
Merging 20ec305b7b2e52f3830f44b7da4ac6bc0a881389 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
69977e4
Merging 1ba5bf2eb10db38cee8dcafca9bb709d14f38136 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
1229cb4
Merging 967f5ba0b4429faf68a3238288fb332d08ea511e into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
a228850
Merging 664c40913ac5e1a781e5558a8240bc489f292901 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
7a72e9a
Merging e235d2b3d2b1a339adea506530550ba46573fe86 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
c07d7d1
Merging 7a72e9afd628a09c27ed2cdfdd4ca35f7a503ac7 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
a1ebffa
Merging 187fb3af4a71e7d19cf71a5cc8d8756535bd657e into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
8b131ad
Merging 0d83ab6db5f3bff15416419f73293e43cbbdf05a into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
e7a0148
Merging 7a72e9afd628a09c27ed2cdfdd4ca35f7a503ac7 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
88f62e4
Merging 8b131ad869b94be79b8b4a9a20f5bd48695f9fbf into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
ffa1f8c
Merging 4ef37fac52ba77ded3dfb929a3c238741a1f6a0d into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
5ca2559
Merging 4e7361fcb32a59995600c6ab859af3b2b3cbbfad into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
4ef7255
Merging b31246f4d4e27448a077162e753b4601448263c7 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
ed4dd8d
Merging db91b86f62b132444c7fb619b257ae52921582d1 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
4853668
Merging ed4dd8d0998334b375e4d06d75736fc2eccc3f95 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
02c2969
Merging 5d44f30c5ea9e785c9fbdab92a785238fe5c79cf into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
9773b2d
Merging 2947928fb972cb4e891974047122c3a066ff1ff1 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
3417b7f
Merging 79304ff1b70d55369d33fb5754e03bfb4fc2c9de into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
3075ca8
Merging a39a9d014573b7cf36445b9cd435f4796e8c3ce6 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
276dddf
Merging 3075ca8402463e9d434f9e74afc16e8a02e04276 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
2e00c06
Merging 88928c1a54704fd5a5d95c67f872313b7a1d0488 into trunk-temp/pr-1…
trunk-io[bot] Oct 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions .github/scripts/check-snapshot-baseline-removals.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { appendFileSync, readFileSync } from 'node:fs'
import { posix } from 'node:path'
import { pathToFileURL } from 'node:url'
import { parseArgs } from 'node:util'

const BASELINE_PATH = 'frontend/snapshots.yml'

// Returns each snapshot id with its lines as written, so a failure can print them for restoring.
// The YAML writer emits a key over 128 characters in the explicit `? key` form.
export function readSnapshotEntries(yamlText) {
const entries = new Map()
let inSnapshots = false
let current = null
for (const line of yamlText.split('\n')) {
if (/^\s*(#|$)/.test(line)) {
continue
}
if (/^\S/.test(line)) {
inSnapshots = /^snapshots:\s*(#.*)?$/.test(line)
current = null
continue
}
if (!inSnapshots) {
continue
}
const key = /^ {4}(?:\? +(['"]?)([^\s'":?]+)\1|(['"]?)([^\s'":?]+)\3:)(?:\s+#.*)?\s*$/.exec(line)
if (key) {
current = [line]
entries.set(key[2] ?? key[4], current)
} else if (current) {
current.push(line)
}
}
return entries
}

// Storybook sanitizes a story id to exactly one `--` (title--export), and the test runner appends
// `--<viewport width>`, `--<theme>` and `--<browser>` to it, so the first two segments name the story.
export function storyIdOf(snapshotId) {
return snapshotId.split('--').slice(0, 2).join('--')
}

export function readSnapshottedStories(indexJsonText, storybookRoot) {
const entries = JSON.parse(indexJsonText).entries ?? {}
const stories = new Map()
for (const entry of Object.values(entries)) {
if (entry?.type !== 'story' || (entry.tags ?? []).includes('test-skip')) {
continue
}
stories.set(entry.id, entry.importPath ? posix.normalize(posix.join(storybookRoot, entry.importPath)) : null)
}
return stories
}

// A story the PR's Visual Review run renders shows a wrong removal there for review. Only stories
// that run skips can lose every entry silently.
export function findLiveRemovals({ baseIds, headIds, stories, renderedFiles }) {
const storiesWithHeadEntries = new Set([...headIds].map(storyIdOf))
const removals = []
for (const id of baseIds) {
if (headIds.has(id)) {
continue
}
const storyId = storyIdOf(id)
if (!stories.has(storyId) || storiesWithHeadEntries.has(storyId) || renderedFiles.has(stories.get(storyId))) {
continue
}
removals.push(id)
}
return removals.sort()
}

export function formatFailure(removals, baseEntries) {
const storyCount = new Set(removals.map(storyIdOf)).size
const stories = storyCount === 1 ? 'story that still exists' : 'stories that still exist'
return [
`This PR removes every ${BASELINE_PATH} entry of ${storyCount} ${stories}.`,
"This PR's Visual Review run does not render these stories, so it cannot show the removal.",
'The merge queue renders every story and rejects the batch that holds this PR.',
'',
`To fix it, add these lines back to ${BASELINE_PATH} under snapshots, in alphabetical order:`,
'',
...removals.flatMap((id) => baseEntries.get(id)),
'',
"If a story should no longer take snapshots, tag it 'test-skip' in its story file and this check passes.",
].join('\n')
}

function main() {
const { values } = parseArgs({
options: {
'base-snapshots': { type: 'string' },
'head-snapshots': { type: 'string' },
'storybook-index': { type: 'string' },
'storybook-root': { type: 'string', default: 'common/storybook' },
'rendered-files': { type: 'string' },
},
})
for (const required of ['base-snapshots', 'head-snapshots', 'storybook-index', 'rendered-files']) {
if (!values[required]) {
console.error(`Missing --${required}`)
process.exit(2)
}
}

const baseEntries = readSnapshotEntries(readFileSync(values['base-snapshots'], 'utf8'))
const removals = findLiveRemovals({
baseIds: new Set(baseEntries.keys()),
headIds: new Set(readSnapshotEntries(readFileSync(values['head-snapshots'], 'utf8')).keys()),
stories: readSnapshottedStories(readFileSync(values['storybook-index'], 'utf8'), values['storybook-root']),
renderedFiles: new Set(JSON.parse(readFileSync(values['rendered-files'], 'utf8'))),
})
if (removals.length === 0) {
console.log(`No ${BASELINE_PATH} entry was removed for a story that still takes snapshots.`)
return
}

const message = formatFailure(removals, baseEntries)
console.log(message)
if (process.env.GITHUB_STEP_SUMMARY) {
appendFileSync(
process.env.GITHUB_STEP_SUMMARY,
`### Snapshot baselines removed\n\n\`\`\`\n${message}\n\`\`\`\n`
)
}
if (process.env.GITHUB_ACTIONS) {
const annotation = message.replaceAll('%', '%25').replaceAll('\r', '%0D').replaceAll('\n', '%0A')
console.log(`::error title=Snapshot baselines removed for existing stories::${annotation}`)
}
process.exit(1)
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main()
}
130 changes: 130 additions & 0 deletions .github/scripts/check-snapshot-baseline-removals.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import assert from 'node:assert/strict'
import test from 'node:test'

import {
findLiveRemovals,
formatFailure,
readSnapshotEntries,
readSnapshottedStories,
} from './check-snapshot-baseline-removals.mjs'

const LONG_KEY_LENGTH = 60

const entryLines = (id) => {
const comment = id.endsWith('--light') ? ' # approved' : ''
return id.length > LONG_KEY_LENGTH
? [` ? ${id}${comment}`, ` : hash: v1.k1.${id.length}.fake`]
: [` ${id}:${comment}`, ` hash: v1.k1.${id.length}.fake`]
}

const baselineYaml = (ids) =>
[
'version: 1',
'config:',
' api: https://us.example.com',
" team: '2'",
'snapshots: # approved baselines',
...ids.flatMap(entryLines),
'# end of baselines',
'',
].join('\n')

const indexJson = (stories) =>
JSON.stringify({
v: 5,
entries: Object.fromEntries([
[
'settings-form--docs',
{ type: 'docs', id: 'settings-form--docs', importPath: '../../frontend/src/SettingsForm.mdx' },
],
...stories.map(({ id, file, tags = ['dev', 'test'] }) => [
id,
{ type: 'story', id, importPath: `../../${file}`, tags },
]),
]),
})

const LONG_STORY = 'products-dashboards-widget-types-error-tracking-settings--before-ingestion'

const BASE = [
`${LONG_STORY}--dark`,
`${LONG_STORY}--light`,
'settings-form--edited--dark',
'settings-form--edited--light',
'settings-form--empty--dark',
'settings-form--empty--light',
'widget-grid--default--narrow--dark',
'widget-grid--default--narrow--light',
]

const STORIES = [
{ id: LONG_STORY, file: 'products/dashboards/frontend/ErrorTrackingSettings.stories.tsx' },
{ id: 'settings-form--edited', file: 'frontend/src/SettingsForm.stories.tsx' },
{ id: 'settings-form--empty', file: 'frontend/src/SettingsForm.stories.tsx' },
{ id: 'widget-grid--default', file: 'products/widgets/frontend/WidgetGrid.stories.tsx' },
]

const cases = [
{
name: 'flags every entry of an existing story the PR run does not render',
head: BASE.filter((id) => !id.startsWith('settings-form--edited')),
stories: STORIES,
renderedFiles: [],
expected: ['settings-form--edited--dark', 'settings-form--edited--light'],
},
{
name: 'flags entries written with explicit keys',
head: BASE.filter((id) => !id.startsWith(LONG_STORY)),
stories: STORIES,
renderedFiles: [],
expected: [`${LONG_STORY}--dark`, `${LONG_STORY}--light`],
},
{
name: 'passes when the story was deleted or renamed',
head: BASE.filter((id) => !id.startsWith('widget-grid')),
stories: STORIES.filter((story) => story.id !== 'widget-grid--default'),
renderedFiles: [],
expected: [],
},
{
name: 'passes when the story keeps another variant',
head: BASE.filter((id) => id !== 'widget-grid--default--narrow--dark'),
stories: STORIES,
renderedFiles: [],
expected: [],
},
{
name: 'passes when the story is tagged test-skip',
head: BASE.filter((id) => !id.startsWith('widget-grid')),
stories: STORIES.map((story) =>
story.id === 'widget-grid--default' ? { ...story, tags: ['dev', 'test', 'test-skip'] } : story
),
renderedFiles: [],
expected: [],
},
{
name: 'passes when the PR run renders the story',
head: BASE.filter((id) => !id.startsWith('widget-grid')),
stories: STORIES,
renderedFiles: ['products/widgets/frontend/WidgetGrid.stories.tsx'],
expected: [],
},
]

for (const { name, head, stories, renderedFiles, expected } of cases) {
test(name, () => {
const removals = findLiveRemovals({
baseIds: new Set(readSnapshotEntries(baselineYaml(BASE)).keys()),
headIds: new Set(readSnapshotEntries(baselineYaml(head)).keys()),
stories: readSnapshottedStories(indexJson(stories), 'common/storybook'),
renderedFiles: new Set(renderedFiles),
})
assert.deepEqual(removals, expected)
})
}

test('the failure prints the removed entries exactly as the base file wrote them', () => {
const removed = [`${LONG_STORY}--dark`, 'settings-form--edited--dark']
const message = formatFailure(removed, readSnapshotEntries(baselineYaml(BASE)))
assert.ok(message.includes(removed.flatMap(entryLines).join('\n')))
})
31 changes: 31 additions & 0 deletions .github/scripts/post-ci-sections.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { describe, it } from 'node:test'

import { buildDocsPreviewSection } from './post-docs-preview-section.mjs'
import { buildHobbySection } from './post-hobby-section.mjs'
import { buildHogboxPreviewSection } from './post-hogbox-preview-section.mjs'
import { buildTrunkLaneSection, postTrunkLaneSection } from './post-trunk-lane-section.mjs'

const commonHobby = {
Expand Down Expand Up @@ -123,6 +124,36 @@ describe('CI report section builders', () => {
assert.match(section.body, /actions\/runs\/42/)
})

for (const testCase of [
{
name: 'claims the PR frontend only when it was swapped in',
frontendSwapped: true,
expected: /\*\*and\*\* frontend/,
},
{
name: 'says the frontend is unchanged when it was not swapped in',
frontendSwapped: false,
expected: /frontend unchanged by this PR/,
},
]) {
it(testCase.name, () => {
const section = buildHogboxPreviewSection({
state: 'ready',
sha: '1234567890abcdef',
runUrl: 'https://github.com/PostHog/posthog/actions/runs/42',
url: 'https://preview.example.com',
boxId: 'box-1',
penId: 'None',
consoleHost: 'console.example.com',
frontendSwapped: testCase.frontendSwapped,
})
assert.equal(section.status, 'ok')
assert.match(section.summary, /https:\/\/preview\.example\.com/)
assert.match(section.body, testCase.expected)
assert.doesNotMatch(section.body, /console\/fleet\/pens/)
})
}

it('moves a hobby preview through setup, ready, and failed states', () => {
const initial = buildHobbySection({ state: 'initial', ...commonHobby })
const ready = buildHobbySection({
Expand Down
Loading
Loading