Skip to content

ci: enforce that every workspace declares what it imports - #423

Merged
qnbs merged 9 commits into
mainfrom
ci/phantom-dependency-guard
Jul 14, 2026
Merged

ci: enforce that every workspace declares what it imports#423
qnbs merged 9 commits into
mainfrom
ci/phantom-dependency-guard

Conversation

@qnbs

@qnbs qnbs commented Jul 14, 2026

Copy link
Copy Markdown
Owner

What

Adds check:phantom-deps, a CI gate that fails when a workspace imports a package it does not declare — and fixes every violation it found.

Why

pnpm-workspace.yaml sets shamefullyHoist: true, which flattens transitive dependencies into the root node_modules. An undeclared import therefore still resolves — until the package that dragged it in drops it, or hoisting is turned off. Then it breaks at runtime, with nothing in the diff to explain why.

This is not hypothetical. In #413, @sentry/browser was removed from apps/web by a dependency bump while five source files still imported it directly. It kept working purely because @sentry/react happened to pull it in. CodeRabbit flagged the same class of risk on shamefullyHoist in that PR; this is the guard it asked for.

Running it against main found five more.

What the guard found

Package Imported by Declared in Resolution
@xenova/transformers apps/web (4 files) packages/ai-core (optional) Routed through ai-core's loadTransformers()
@mlc-ai/web-llm apps/web packages/ai-core (optional) Routed through ai-core's loadWebLlm()
@tauri-apps/plugin-dialog apps/web apps/desktop Declared as an optionalDependency of apps/web
@tauri-apps/plugin-notification apps/web apps/desktop Declared as an optionalDependency of apps/web
@tensorflow/tfjs apps/web nowhere Dead path removed — see below

The TensorFlow finding

predictYield() dynamically imported @tensorflow/tfjs once a grower had 10 or more recorded harvests. That package is declared in no package.json in this repo and is not installed, so the import could only ever reject.

The feature threw for exactly the users who had used the app the longest.

Three layers of defence missed it:

  • the typecheck was blinded by an ambient declare module '@tensorflow/tfjs' in optional-deps.d.ts,
  • the tests only exercise the zero-sample path, which returns before the import,
  • the bundler never resolved it, because the import is dynamic.

The path is removed rather than repaired: making it work meant shipping a multi-megabyte runtime to train a three-layer network on a handful of samples. predictYield() is now heuristic throughout — which is what every user was already getting, minus the exception. Recorded harvests still raise confidence.

This also deletes the ambient declaration and its 11 no-explicit-any suppressions.

How the guard works

For each workspace it extracts external imports from the sources and checks them against that workspace's own package.json — never against whatever happens to sit in node_modules.

Precision measures, since a noisy gate gets disabled:

  • comments are stripped first, so a commented-out import or a JSDoc @type {import('x')} is not counted as a dependency;
  • specifiers are validated against a bare-specifier pattern, so a regex that wandered across a line boundary reports nothing rather than a nonsense package name;
  • self-references through a package's own exports are ignored;
  • test, config and setup files may import devDependencies; runtime files may not — hoisting hides that just as effectively.

It runs in the quality job before the test suite: it is a sub-second static check and should fail early.

Verification

  • pnpm run check:phantom-depsOK, zero findings, zero false positives.
  • turbo run typecheck5/5 green.
  • yieldPredictionService tests → green.
  • ESLint on every changed source file → 0 errors.
  • Lockfile diff is exactly 6 lines (the two Tauri plugins); no incidental version bumps.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added optional desktop dialog and notification support.
  • Updates
    • Yield forecasting now uses heuristic projections only; ML-specific status messaging was removed and the forecast UI/explanations were refreshed across supported languages.
    • Forecast confidence is now displayed as a percentage (and the “no history” vs heuristic confidence text was clarified).
    • Tech-stack credits now reference ONNX Runtime Web only.
  • Tests
    • Updated yield prediction tests to assert heuristic-only behavior.
  • Chores
    • CI now enforces declared runtime imports.

qnbs and others added 4 commits July 14, 2026 10:56
apps/web imported @xenova/transformers and @mlc-ai/web-llm directly while
declaring neither. They resolved only because shamefullyHoist flattens
ai-core's optionalDependencies into the root node_modules -- the same latent
breakage that #413 fixed for onnxruntime-web.

Both now go through ai-core's lazy loaders (loadTransformers, loadWebLlm), and
the module types derive from those loaders' return types instead of a direct
`typeof import`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tauri-apps/plugin-dialog and @tauri-apps/plugin-notification were declared by
apps/desktop and imported by apps/web, which resolved through hoisting alone.

Declared as optionalDependencies, matching @tauri-apps/api: both are reached
only through dynamic imports guarded by a Tauri-context check, so a plain web
install must not fail without them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
predictYield() dynamically imported @tensorflow/tfjs once a grower had 10 or
more recorded harvests. @tensorflow/tfjs is declared in no package.json in this
repo and is not installed, so that import could only ever reject: the feature
threw for exactly the users who had used the app the longest.

Nothing caught it. The typecheck was blinded by an ambient `declare module
'@tensorflow/tfjs'` in optional-deps.d.ts, the tests only exercise the zero-
sample path, and the import is dynamic so the bundler never resolved it.

The path is removed rather than repaired: making it work meant shipping a
multi-megabyte runtime to train a three-layer network on a handful of samples.
predictYield() is now heuristic throughout -- which is what every user was
already getting, minus the exception. Recorded harvests still raise confidence.

Removes: the model code, the ambient declaration (and its 11 no-explicit-any
suppressions), the `usedTensorflowModel` field, the vite external, and the UI
branch plus i18n strings that promised a local model, across all five locales.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
shamefullyHoist flattens transitive dependencies into the root node_modules, so
an undeclared import resolves anyway -- until the package that dragged it in
drops it, and then it breaks at runtime with nothing in the diff to explain why.
@sentry/browser reached that state in #413; this pass found five more.

check:phantom-deps walks each workspace, extracts the external imports from its
sources, and checks them against that workspace's own package.json rather than
against whatever happens to sit in node_modules. Comments are stripped first, so
a commented-out import or a JSDoc `@type {import('x')}` is not a dependency, and
specifiers are validated so a regex that wandered across a line boundary reports
nothing instead of a nonsense package name.

It also flags runtime code importing a devDependency, which hoisting hides just
as effectively.

Runs in the quality job before the test suite: it is a sub-second static check
and should fail early.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
canna-guide-2025-web Ready Ready Preview, Comment Jul 14, 2026 12:11pm

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@qnbs, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 29 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 07a2b5ec-01aa-423f-92e9-4b59b2ab234d

📥 Commits

Reviewing files that changed from the base of the PR and between 1e78b32 and 599b59f.

📒 Files selected for processing (1)
  • apps/web/components/views/plants/GrowStatsDashboard.tsx
📝 Walkthrough

Walkthrough

TensorFlow-based yield prediction was removed in favor of heuristic projections. Local ML modules now load through @cannaguide/ai-core/ml, while CI adds enforcement for undeclared workspace dependencies.

Changes

ML prediction and UI

Layer / File(s) Summary
Heuristic yield prediction and dashboard messaging
packages/ai-core/src/domain/diagnosis.ts, apps/web/services/yieldPredictionService.ts, apps/web/services/yieldPredictionService.test.ts, apps/web/components/views/plants/GrowStatsDashboard.tsx, apps/web/locales/*/plants.ts
Yield prediction now always uses heuristic calculations, removes usedTensorflowModel, updates tests, removes model-status rendering, and revises localized forecast messaging.

Centralized ML loading

Layer / File(s) Summary
Centralized optional ML loading
apps/web/services/local-ai/..., apps/web/workers/*, apps/web/package.json, apps/web/types/optional-deps.d.ts, apps/web/vite.config.ts, apps/web/locales/*/settings.ts
Transformers.js and WebLLM are loaded through ai-core helpers, with optional dependency declarations, ambient types, build handling, and ONNX Runtime credits updated.

Declared-dependency enforcement

Layer / File(s) Summary
Declared-dependency enforcement
scripts/check-phantom-deps.mjs, scripts/check-file-budget.mjs, package.json, .github/workflows/ci.yml
A workspace import scanner reports undeclared or misplaced dependencies, file-budget scope handling is refined, and CI runs the dependency check.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CI
  participant PhantomDependencyChecker
  participant WorkspaceSources
  participant PackageManifests
  CI->>PhantomDependencyChecker: run check:phantom-deps
  PhantomDependencyChecker->>WorkspaceSources: collect source imports
  PhantomDependencyChecker->>PackageManifests: read declared dependencies
  PhantomDependencyChecker-->>CI: report phantom or misplaced imports
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main CI change: adding enforcement that each workspace declares its imports.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/phantom-dependency-guard

Comment @coderabbitai help to get the list of available commands.

@deepsource-io

deepsource-io Bot commented Jul 14, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in b6cc738...599b59f on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

Important

Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
Docker Jul 14, 2026 12:10p.m. Review ↗
JavaScript Jul 14, 2026 12:10p.m. Review ↗
Python Jul 14, 2026 12:10p.m. Review ↗
Rust Jul 14, 2026 12:10p.m. Review ↗
Shell Jul 14, 2026 12:10p.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

#414 split the quality job into build/test/verify. The declared-dependency gate
moves into `verify`, next to the service-dependency acyclic check -- both are
dependency-graph gates, and verify is where the other static checks live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/locales/es/plants.ts`:
- Around line 617-618: Update the mlForecast translation to a standalone
forecast label in all affected locale files: apps/web/locales/es/plants.ts lines
617-618 use “Pronostico”, apps/web/locales/fr/plants.ts lines 619-620 use
“Prevision”, and apps/web/locales/nl/plants.ts lines 603-604 use “Voorspelling”;
leave the confidence translations unchanged because the consumer appends them
separately.

In `@apps/web/services/yieldPredictionService.ts`:
- Around line 148-158: Update the sample-count logic in the yield prediction
flow around collectTrainingSamples so confidence and the explanation use the
number of actual harvested plants, not historical snapshots or the final plant
vector. Preserve the zero-harvest fallback, and add a regression test covering
one harvested plant with multiple history entries to ensure it is counted once
and reported as one recorded harvest.
- Around line 155-158: Update the prediction explanation branch in
yieldPredictionService to resolve both messages through the centralized getT
mechanism from apps/web/i18n.ts instead of returning inline English strings. Add
stable corresponding translation keys to the EN, DE, ES, FR, and NL locale
resources, preserving the sampleCount-specific wording and interpolation of the
recorded harvest count.

In `@scripts/check-phantom-deps.mjs`:
- Around line 129-133: Update the manifest-loading try/catch in the workspace
discovery flow to ignore only a genuinely missing package.json. Re-throw
malformed JSON and other I/O errors instead of treating them as non-workspaces,
and replace the empty catch behavior with explicit error discrimination while
preserving the existing packages.push path.
- Line 76: Update the import parsing regex and its consumers in the
dependency-checking flow to capture whether each import is type-only, rather
than discarding the type marker. In the runtime dependency validation around the
import handling at lines 163-165, skip checks for type-only imports while
preserving existing validation for runtime imports.
- Around line 87-99: Update stripComments and the importedPackages scanning flow
to remove or mask quoted strings and template literals, including their
contents, before applying IMPORT_PATTERNS. Preserve real import detection while
ensuring import-like text inside single-quoted, double-quoted, or backtick
literals is ignored; use a lexer/parser approach if needed for correct escaping
and multiline handling.
- Around line 66-68: Normalize import specifiers by removing the query portion
before applying BARE_SPECIFIER.test(), ensuring query-bearing imports reach
packageNameOf() and are checked like bare package imports. Preserve the existing
package-name extraction behavior for scoped and unscoped specifiers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8ae73780-7a31-4951-992b-7a90d97b24f8

📥 Commits

Reviewing files that changed from the base of the PR and between 572fa47 and abee9c1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (25)
  • .github/workflows/ci.yml
  • apps/web/components/views/plants/GrowStatsDashboard.tsx
  • apps/web/locales/de/plants.ts
  • apps/web/locales/de/settings.ts
  • apps/web/locales/en/plants.ts
  • apps/web/locales/en/settings.ts
  • apps/web/locales/es/plants.ts
  • apps/web/locales/es/settings.ts
  • apps/web/locales/fr/plants.ts
  • apps/web/locales/fr/settings.ts
  • apps/web/locales/nl/plants.ts
  • apps/web/locales/nl/settings.ts
  • apps/web/package.json
  • apps/web/services/local-ai/models/modelLoader.ts
  • apps/web/services/local-ai/models/webLlmService.ts
  • apps/web/services/local-ai/nlp/whisperService.ts
  • apps/web/services/yieldPredictionService.test.ts
  • apps/web/services/yieldPredictionService.ts
  • apps/web/types/optional-deps.d.ts
  • apps/web/vite.config.ts
  • apps/web/workers/imageGeneration.worker.ts
  • apps/web/workers/inference.worker.ts
  • package.json
  • packages/ai-core/src/domain/diagnosis.ts
  • scripts/check-phantom-deps.mjs
💤 Files with no reviewable changes (3)
  • apps/web/vite.config.ts
  • packages/ai-core/src/domain/diagnosis.ts
  • apps/web/components/views/plants/GrowStatsDashboard.tsx

Comment thread apps/web/locales/es/plants.ts Outdated
Comment thread apps/web/services/yieldPredictionService.ts Outdated
Comment thread apps/web/services/yieldPredictionService.ts Outdated
Comment thread scripts/check-phantom-deps.mjs Outdated
Comment thread scripts/check-phantom-deps.mjs Outdated
Comment thread scripts/check-phantom-deps.mjs Outdated
Comment thread scripts/check-phantom-deps.mjs Outdated
@qnbs
qnbs marked this pull request as ready for review July 14, 2026 09:49
check-phantom-deps:
- Vite query suffixes (`import('pkg?worker')`) were rejected by the
  bare-specifier check before packageNameOf() could strip them, so those imports
  were never checked at all. Strip the query first. A fixture confirms
  `undeclared-worker?worker` is now caught.
- Type-only imports were indistinguishable from value imports, so
  `import type { X } from 'some-dev-dep'` in runtime code was reported as
  reaching into a devDependency. Types are erased at compile time, so that is
  legitimate. The two kinds are now tracked separately; a type-only import must
  still be declared, but may be declared as a devDependency.
- stripComments() left string and template content, so `const s = "import('x')"`
  could be counted as a dependency. The leading guard now also rejects a match
  that begins right after a quote or backtick.
- A package.json that exists but fails to parse was swallowed by a bare catch,
  which would silently disable the check for that workspace. It now exits 1 and
  says which file. A missing package.json still just means "not a workspace".

check-file-budget:
- The changed-file path ignored SCAN_GLOBS entirely, so touching any long file
  outside application source failed the gate. A locale dictionary is ~1000 lines
  by nature and no amount of splitting changes that; the budget was never meant
  to police it. The changed-file path now honours the same scope as the full
  scan, and "diff touched nothing in scope" no longer escalates to a full scan.

yieldPredictionService:
- collectTrainingSamples() emits one sample per history *snapshot* of each
  harvested plant, not one per harvest, so "recorded harvests" was wrong. Say
  samples, which is what the UI already calls them.
- The explanation was a hardcoded English string on a user-facing field. Routed
  through getT(), like chemotypeService, with keys in all five locales.

GrowStatsDashboard:
- confidence is a 0..1 fraction, and `confidence.toFixed(0)` rendered a flat
  "0%" for every prediction the app has ever made. Scale by 100 first.
- `mlForecast` + `confidence` rendered as "Forecast confidence 45% confidence".
  mlForecast is now the leading noun, so the sentence reads "Forecast 45%
  confidence".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread apps/web/services/yieldPredictionService.ts Outdated
DeepSource JS-0116: single-letter `t`. `t` is the i18next convention and reads
fine, but the rule is enforced and a suppression would spend the ratchet on
nothing. `translate` costs nothing either.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/components/views/plants/GrowStatsDashboard.tsx`:
- Around line 149-150: Update the comment immediately before the confidence
conversion in the GrowStatsDashboard rendering logic to accurately describe the
issue with rounding a 0–1 confidence fraction using toFixed(0), without claiming
all predictions rendered 0%. Leave the conversion on the following line
unchanged.

In `@apps/web/locales/es/plants.ts`:
- Around line 617-626: Update the new Spanish strings in the translation entries
around mlForecast, heuristicFallback, modelTraining, modelIdle,
explanationNoHistory, and explanationHeuristic to preserve all required
diacritics using ASCII-compatible Unicode escapes such as \u00f3 and \u00fa.
Keep the source file ASCII-only while ensuring the rendered text displays the
correctly accented Spanish words.

In `@scripts/check-file-budget.mjs`:
- Around line 103-105: Update the diff handling around gitDiffFiles so the raw
changed-file list is retained separately from the filtered budget-file list. Use
the raw diff’s non-empty status to choose between scanning the filtered changed
files and allTrackedInScanDirs(), ensuring out-of-scope-only changes do not
trigger a full scan.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e873cf34-984f-49ad-90f2-c75bd96aa683

📥 Commits

Reviewing files that changed from the base of the PR and between abee9c1 and fdadcfc.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • .github/workflows/ci.yml
  • apps/web/components/views/plants/GrowStatsDashboard.tsx
  • apps/web/locales/de/plants.ts
  • apps/web/locales/en/plants.ts
  • apps/web/locales/es/plants.ts
  • apps/web/locales/fr/plants.ts
  • apps/web/locales/nl/plants.ts
  • apps/web/services/yieldPredictionService.ts
  • package.json
  • scripts/check-file-budget.mjs
  • scripts/check-phantom-deps.mjs
🚧 Files skipped from review as they are similar to previous changes (6)
  • package.json
  • apps/web/locales/fr/plants.ts
  • apps/web/locales/en/plants.ts
  • apps/web/locales/de/plants.ts
  • apps/web/services/yieldPredictionService.ts
  • scripts/check-phantom-deps.mjs

Comment thread apps/web/components/views/plants/GrowStatsDashboard.tsx Outdated
Comment thread apps/web/locales/es/plants.ts Outdated
Comment thread scripts/check-file-budget.mjs Outdated
qnbs and others added 2 commits July 14, 2026 13:58
check-file-budget: the fallback decision was still wrong. gitDiffFiles() already
filters to ts/tsx/mjs and drops tests, so a branch touching only package.json, a
workflow, docs or tests produced an empty list -- which then escalated to a full
scan, the exact opposite of "out of scope". Split the raw diff (does this branch
change anything?) from the budget-eligible subset (what can the budget apply to?).

Locales: the new es/fr/de strings shipped without diacritics. These files encode
accents as ASCII \uXXXX escapes -- ó appears 70 times in es alone, ä 58
times in de -- so "Pronostico" and "Schaetzung" were simply misspelt. Escaped
properly. (The accent-free strings already in those files predate this branch and
are left alone; correcting them belongs in its own pass.)

GrowStatsDashboard: the comment overstated the bug. toFixed(0) on a 0..1 fraction
rounds to a single digit, so predictions rendered as "0%" or "1%" -- not "0%" for
every prediction. The fix is unchanged; the comment now says what actually happened.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DeepSource JS-R1005: GrowStatsDashboardComponent sits at a cyclomatic complexity
of 8. The forecast copy carries three of those branches on its own -- prediction
or not, loading or not, explanation or fallback -- and none of them are about the
dashboard.

Moved into YieldForecastCard, which owns them. No behaviour change; the parent
just stops paying for branches that belong to one of its cards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@qnbs

qnbs commented Jul 14, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@qnbs
qnbs merged commit 9648ac9 into main Jul 14, 2026
33 checks passed
@qnbs
qnbs deleted the ci/phantom-dependency-guard branch July 14, 2026 13:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant