From 82588e258a1a87986f65df5f73e11f20c35339ce Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Mon, 3 Aug 2026 07:55:50 -0400 Subject: [PATCH 1/2] chore(ci): ignore tool caches when listing products for lane names ruff and pytest leave .ruff_cache and __pycache__ next to the products, so a local run of the target script invented a lane for each. CI is unaffected: a fresh checkout holds only tracked directories and the compute job runs no Python. This keeps a local run answering the same as CI. Co-Authored-By: Claude Opus 5 (1M context) --- .github/scripts/trunk-impacted-targets.js | 17 ++++++++++++++--- .github/scripts/trunk-impacted-targets.test.js | 13 +++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/.github/scripts/trunk-impacted-targets.js b/.github/scripts/trunk-impacted-targets.js index 4f4bf9de3cac..679056d9a770 100644 --- a/.github/scripts/trunk-impacted-targets.js +++ b/.github/scripts/trunk-impacted-targets.js @@ -174,10 +174,21 @@ function isTripwire(file) { return TRIPWIRE_MATCHERS.some((re) => re.test(file)) } +// Tool caches share the directory with the products, so a local run picks up +// .ruff_cache and __pycache__ as products and invents a lane for each. CI never +// sees them — a fresh checkout holds only tracked directories and this job runs +// no Python — so this keeps a local run answering the same as CI rather than +// fixing a live miscount. Dropping a real product would only ever widen, since +// an unrecognized product name falls through to ALL, so the filter is safe in +// the one direction it can be wrong. +function isProductDirectory(name) { + return !name.startsWith('.') && !name.startsWith('__') && name !== 'node_modules' +} + function listProducts(repoRoot) { return fs .readdirSync(path.join(repoRoot, 'products'), { withFileTypes: true }) - .filter((entry) => entry.isDirectory()) + .filter((entry) => entry.isDirectory() && isProductDirectory(entry.name)) .map((entry) => entry.name) .sort() } @@ -275,8 +286,7 @@ function compileContractMatcher(inputs) { if (include.length === 0) { return null } - return (relativePath) => - include.some((re) => re.test(relativePath)) && !exclude.some((re) => re.test(relativePath)) + return (relativePath) => include.some((re) => re.test(relativePath)) && !exclude.some((re) => re.test(relativePath)) } // Only products that narrow `backend:contract-check` in their own turbo.json get @@ -763,6 +773,7 @@ module.exports = { buildContext, compileContractMatcher, globToRegExp, + isProductDirectory, isTripwire, parseCrateDependencies, parseCrateName, diff --git a/.github/scripts/trunk-impacted-targets.test.js b/.github/scripts/trunk-impacted-targets.test.js index c366416e291d..69ba37bef0f5 100644 --- a/.github/scripts/trunk-impacted-targets.test.js +++ b/.github/scripts/trunk-impacted-targets.test.js @@ -14,6 +14,7 @@ const { computeTargets, compileContractMatcher, globToRegExp, + isProductDirectory, isTripwire, parseCrateDependencies, reverseClosure, @@ -301,6 +302,18 @@ test('product frontend and backend changes land in separate domains', () => { assert.deepEqual(computeTargets(['products/beta/backend/api.py'], CONTEXT), ['py:product:beta']) }) +// ruff and pytest leave caches next to the products, and a run that treats them +// as products invents a lane per cache. Nothing downstream rejects a nonsense +// target name, so the only symptom is a local run disagreeing with CI. +test('tool caches beside the products are not products', () => { + for (const name of ['.ruff_cache', '.pytest_cache', '__pycache__', 'node_modules']) { + assert.equal(isProductDirectory(name), false, name) + } + for (const name of ['surveys', 'error_tracking', 'desktop']) { + assert.equal(isProductDirectory(name), true, name) + } +}) + test('a product file that is neither backend nor frontend claims both domains', () => { const targets = computeTargets(['products/beta/mcp/tools.yaml'], CONTEXT) assert.equal(targets.includes('py:product:beta'), true) From 3b75fa7e05c569e5e799ab2d8a6d403f7a528c94 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi <3247106+gantoine@users.noreply.github.com> Date: Mon, 3 Aug 2026 09:16:41 -0400 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/scripts/trunk-impacted-targets.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/scripts/trunk-impacted-targets.js b/.github/scripts/trunk-impacted-targets.js index 679056d9a770..778461fbe882 100644 --- a/.github/scripts/trunk-impacted-targets.js +++ b/.github/scripts/trunk-impacted-targets.js @@ -174,13 +174,13 @@ function isTripwire(file) { return TRIPWIRE_MATCHERS.some((re) => re.test(file)) } -// Tool caches share the directory with the products, so a local run picks up -// .ruff_cache and __pycache__ as products and invents a lane for each. CI never -// sees them — a fresh checkout holds only tracked directories and this job runs -// no Python — so this keeps a local run answering the same as CI rather than -// fixing a live miscount. Dropping a real product would only ever widen, since -// an unrecognized product name falls through to ALL, so the filter is safe in -// the one direction it can be wrong. +// Tool caches share the directory with the products, so a local run can pick up +// directories such as .ruff_cache, .pytest_cache, and __pycache__ as products and +// invent a lane for each. CI never sees them because a fresh checkout has only +// tracked directories and this job does not run Python, so this keeps a local run +// consistent with CI rather than fixing a live miscount. Dropping a real product +// would only ever widen, because an unrecognized product name falls through to +// ALL, so the filter is safe in the one direction it can be wrong. function isProductDirectory(name) { return !name.startsWith('.') && !name.startsWith('__') && name !== 'node_modules' }