Overview
When you switch git branches and they have different components defined which have been read by drupal, there are errors like below. Clearing cache doesn't work so what's the best way to purge this old stuff?
Drupal\Core\Render\Component\Exception\ComponentNotFoundException: Unable to find component "starshot_demo:old-starshot-one-col" in the component repository.
"explicit"
Generally speaking, each component source has its own limiting behavior due to the different … sources:
block→ the block plugin's module being uninstalled is blocked due to its correspondingComponentconfig entity depending on the modulesdc→ the SDC's module/theme being uninstalled is blocked due to its correspondingComponentconfig entity depending on the theme/modulejs→ theJavaScriptComponentconfig entity being deleted/uninstalled is blocked due to its correspondingComponentconfig entity depending on the config entity
"implicit"
This issue is about when a component disappears implicitly:
block→ the block plugin disappearing from a module from one request to another (e.g. when developing, or changing a branch)sdc→ the SDC disappearing from a theme/module from one request to another (e.g. when developing, or changing a branch)js→the JS component config entity disappearingshould not be possible thanks to config dependencies, but when usingdrush config:delete, it totally is possible
Proposed resolution
See #25-#31.
User interface changes
See #26.
| Comment | File | Size | Author |
|---|---|---|---|
| #77 | Screenshot 2025-11-25 at 4.45.47 PM.png | 510.15 KB | wim leers |
| #49 | page_editor_with_props.png | 366.48 KB | tedbow |
| #48 | page_with_deleted_js_code_in_published_page.png | 66.85 KB | tedbow |
| #48 | page_with_deleted_js_code_in_editor.png | 383.55 KB | tedbow |
| #47 | Screenshot 2025-10-09 at 14.15.53.png | 379.36 KB | thoward216 |
Issue fork canvas-3470422
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
lauriiiThis is a standard part of the process so I'm changing this to a bug. In the meanwhile, it would be helpful if we can provide @kristen pol some guidance on how to workaround this until we have a proper solution for this.
Comment #3
wim leersThis is not a bug. Switching git branches and hence suddenly having different git branches is the equivalent of running Drupal with module X installed, switching git branches, and the other git branch not having that module present in the codebase.
Solution: two parts
That being said: yes, we need to guard against this. We need to distinguish between:
tree" equivalent of #3452848: [PP-1] Test coverage to prove configurable fields cannot be deleted from content entity types if they are used in XB Content Type Template (which does "field type present inprops".\Drupal::messenger()to inform the developer with a warning.I defer the exact solution to @f.mazeikis, this is just my 0.02 😊
Unblocking this workflow TODAY
vendor/bin/drush config:delete experience_builder.component.starshot_demo+old-starshot-one-col— but this is one-by-one 😬Comment #4
wim leersThis is closely related to #3464036: Component config entity should validate that the SDC actually (still) exists.
Comment #5
kristen polYay! Happy to have the manual workaround 🙌 I’ll add to our docs.
Comment #6
kristen polAdded here:
https://www.drupal.org/community-initiatives/starshot-demo-design-system...
Thanks!
Comment #7
guptahemant commentedToday during my testing i observed this issue is still present, workaround from #3 did the fix for me.
Comment #8
wim leers@lauriii reported a duplicate at #3473770: Error after updating a theme and @guptahemant spotted that. Crediting both.
Comment #9
wim leersComment #10
lauriiiComment #11
wim leers@longwave opened an issue for essentially the same problem: #3522164: Handle update and delete of SingleDirectoryComponent `Component`s, plus missing config dependencies. I'll let him decide which of the two issues to keep around :) Or maybe he wants to repurpose this for a more explicit DX enhancement, such as a
\Drupal::messenger()-powered warning for SDC developers when Twig dev mode is on? 🤓🤔Comment #12
wim leers#3519168: Handle components provided by ComponentSources EXPLICITLY disappearing — enables deleting JS components that are in use might solve most of this.
Comment #13
wim leersContrasting this with #3519168: Handle components provided by ComponentSources EXPLICITLY disappearing — enables deleting JS components that are in use's updated title.
Comment #14
wim leersMaking it very explicit how this relates to #3519168: Handle components provided by ComponentSources EXPLICITLY disappearing — enables deleting JS components that are in use.
Comment #15
wim leers#3519168: Handle components provided by ComponentSources EXPLICITLY disappearing — enables deleting JS components that are in use is in!
Comment #16
wim leersThe config dependencies part is definitely already taken care of. This is about a module/theme containing an SDC one moment, and not the next.
Comment #17
larowlanComment #18
penyaskitoComment #19
lauriiiComment #20
penyaskitoClosed #3544650: Component definitions not updating when clearing cache in favor of this.
There I created an MR that would "solve" the problem for SDC. That one takes a drastic approach: if a component is not found when rebuilding, it will just delete the component entity.
In #3 this was mentioned as a potential approach if (and only if) the development mode is set.
But here the IS mentions the 3 Canvas-provided cases (blocks, SDCs and even JS components) scenarios.
We cannot rely on this happening only when in development mode. Not best practice, but a block plugin, or an SDC could disappear in a new release of a contrib/custom module/theme, so this wouldn't be only happening in a development scenario.
I wonder if we should:
a) Check if in development: then just delete the component and warn the user.
b) If not:
That's the real complex case. I'd suggest we implement an on-the-fly upgrade process (for existing content) that would convert those to the fallback component keeping its inputs/settings. That's something that was mentioned already for e.g. migrating from layout builder or paragraphs, so it's infrastructure we want to create anyway for any potential component source.
But we still would need to delete the component, as even disabling the component requires the e.g. SDC to exist (is verified on save). If we think this is too drastic, then we would need to try/catch everywhere in canvas and assume that the missing component on discovery is a valid state for a project.
Comment #21
effulgentsia commentedTransferring beta blocker tag from the duplicate issue.
Comment #22
effulgentsia commentedBeta blockers are by definition also stable blockers, but removing the latter tag in order to make it easier to scan a queue of stable blockers that aren't also blockers to beta or RC.
Comment #23
frankied3The workaround in #3 (adjusted for Canvas) was no longer working for me, so I took a crack at it. It looks to me like the addition of folders caused the workaround to stop working, so here's an updated fix for the current build, which is 1.0@alpha at the time of writing.
Comment #24
wim leersPagewith a component tree that contains thecanvas_test_sdc:my-heroSDC/admin/config/development/settings*.component.ymland the Twig file./page/1: no hard crash, just a message that the Twig template is not found:component_pluginskey-value pair in thecache_discoverytable. Now you do get the reported fatal error with stack trace:Comment #25
wim leersShould be caught/handled by the protection layer #3485878 introduced?
Now, why doesn't #3485878: Server-rendered component instances should NEVER result in a user-facing error, should fall back to a meaningful error instead (+ log) catch this? Because if that were to happen, we'd have the same graceful degradation as we have everywhere else:

(Which is IMHO the correct solution, from #20 is impossible without knowing what the ID of the SDC is — multiple could be changing at the same time, and surely we don't want to be guessing automatically? 🫣)
🕵️ So: why not?
The stack trace in the previous comment provides a hint: the crash happens not during actual rendering, but during the collecting of explicit inputs.
The point where the rendering starts is
::toRenderable(). This is a simplified representation of that:The
RenderSafeComponentContainerstuff happens in::renderify(). But you can see in the stack trace, that the crash happens before that point: it happens in the::getHydratedTree()call.💡Attempt #1
So then the naïve solution is: make sure that collecting of inputs happens within the same protection layers.
Trying! 🤓
Comment #26
wim leersBeen pairing with @penyaskito for the past 2 hours. We're doing something what I described in my last comment, but different: rather than adding protection layers to the collecting of inputs, we're instead relying solely on information in the
Componentconfig entity (which is supposed to be the source of the current truth, NOT the filesystem, precisely for situations like this, where the world is changing underneath us!).That got us much further! 🤘
Now you see:

aka the infra that #3485878: Server-rendered component instances should NEVER result in a user-facing error, should fall back to a meaningful error instead (+ log) added! 🥳
We've also gotten the
/canvas/api/v0/config/componentAPI response working again. It now provides the UI with a flag that indicates whether the known component should be available for instantiation (broken: false) or not (broken: true). That should also change the icon in the component list to something conveying brokenness.Tagging to build the UI pieces for that.
Next up: making the component instance form use the "fallback" form, without actually modifying the
Componentconfig entity to switch to the fallback source — that'd be much too disruptive/annoying while developing.Comment #28
penyaskitoPaired for another hour with Wim.
We now have fixed the component instance form to use the "fallback" form, see attachment.
We identified some other ways the reality and the component entity metadata can differ (including not only SDC and blocks, but also JsComponents), and we need to take care of that too.
We also identified that we are missing component metadata in versioned components: what's required and what not. So a follow-up will be created which requires an upgrade path (and new versions yay!)
Comment #29
wim leersFollow-up created for #26: #3548922: A component appearing in the list of components to instantiate that is broken during development should convey that.
Comment #30
wim leers@penyaskito made this work — see his screenshot from #28:

We've now got a — we think, CI will tell 😅 — complete solution for the scope of this issue: gracefully handling of SDCs (dis)appearing from one request to the next, i.e. while developing. And it's built in a
ComponentSource-agnostic way, so it will work for theblock,jsetc. sources too.Test coverage
Still missing: test coverage. Proposed approach: new
ComponentSourceTestBase::testBroken()test. This:ComponentSourceplugin to test it 👍 — yay thanks #3517966: Failing kernel tests for all ways a component source can fail to render on the server side!Pagewith a component tree that includes a single component instance of aComponentin the testedComponentSource. At this point::validate()must pass. Do NOT call::save()yet.Component, append_renamedto its ID, do NOT::validate()(because that would fail — we're trying to simulate this scenario!), but DO call::save(). This is now simulating in tests that the underlying component (whether it's a block plugin, SDC or code component) that actually the developer has just renamed things, causing the component to not exist.Pageto refer to the[original]_renamedComponentby modifying thecomponent_idfield property. This must be saved, but validation should pass too: while theComponentis invalid (it is out of sync with reality), thePageis valid. Just like reported in this issue.$page->url(), it should render what's visible in #25/#26./canvas/api/v0/config/componentshould result in a 200 (500 in HEAD), and the renamedComponentshould be listed, it should have thebroken: trueflag, and itsdefault_markupshould again be what's visible in #25/#26.Comment #31
wim leersOut of scope: handle more breakage scenarios when developing components
There's more things that can fundamentally break a component instance though:
ComponentSource(whether deleted or renamed)type: stringbecoming atype: integer⚠️ Currently, there's also ways to break things in Canvas' UI that are not fundamental, such as passing an optional prop that either no longer exists or has been renamed. (That currently triggers the validation in
\Drupal\canvas\Plugin\Canvas\ComponentSource\GeneratedFieldExplicitInputUxComponentSourceBase::getDefaultStaticPropSource()but arguably should not. See @heyyo's #3532514-18: Gracefully handle components in active development: ensure great DX.)(This may cause garbage values though, for that we have #3524401: `GeneratedFieldExplicitInputUxComponentSourceBase::validateComponentInput()` allows garbage to pile up.)
👆 To fix that: #3532514: Gracefully handle components in active development: ensure great DX,
for which I'm updating the issue title + summary for to more clearly reflect what it's about; should be actionable once this MR lands.→ updated: #3532514-23: Gracefully handle components in active development: ensure great DX.Comment #32
larowlanassuming @penyaskito is asleep I'll push a rough skeleton of the test in #30 so I can expand on it in #3532514: Gracefully handle components in active development: ensure great DX
Comment #33
larowlanPushed the tests per #30 - I don't think they make sense for JSComponents or Personalization so marked them as skipped. Went with a simpler approach than renaming because I think we'll need to alter definitions further in #3532514: Gracefully handle components in active development: ensure great DX and this plumbing lets us do that.
Fixed phpstan while I was at it.
Removing myself from the assigned field because with the basis of these tests in place I can expand on other cases in #3532514: Gracefully handle components in active development: ensure great DX, setting it back to @penyaskito
Comment #34
nagwani commentedComment #35
larowlanPushed two extra commits to a new branch in
3470422-be-nice-for-sdc-developers-larowlanI'm planning to use that approach in #3532514: Gracefully handle components in active development: ensure great DX
Also includes tests (that will fail with the current code in the branch for this issue) for when the missing component has slots and there are child components in those slots.
To resolve that I was forced to go with the approach in
3470422-be-nice-for-sdc-developers-larowlan, which is why I think we need that here. It is a best-effort attempt but without the update path we'll need for #3532514: Gracefully handle components in active development: ensure great DXComment #36
penyaskitoComment #37
wim leersPaired with @penyaskito, and:
BlockComponentchanges work, and we added comments to help the next person understand it too 👍JsComponentTest::testIsBroken()unskipped; we can simulate this problem occurring even for code components despite all protection layers, by directly manipulatingconfig.storageGreen, so merging! 🚀
Comment #38
wim leersComment #40
wim leersAnd it's in!
See y'all in #3532514: Gracefully handle components in active development: ensure great DX next 😊
Comment #42
mayur-sose commentedVerified below scenarios, @wim-leers, please check failed test cases: TC03 and TC08
Error message shown:
Component is missing. Fix the component or copy values to a new component.
Previously stored input
Fallback/placeholder indicates missing component.
Other content/components remain unaffected.
But UI stuck with below error message :
An unexpected error has occurred while fetching the layout.
Error 500: assert($source instanceof ComponentSourceInterface)
Undo last action button does not do anything.
Users are NOT able to instantiate broken components.
Error message shown:
Component is missing. Fix the component or copy values to a new component.
An unexpected error has occurred while fetching the layout.
Error 500: assert($source instanceof ComponentSourceInterface)
Undo last action button does not do anything.
Admin/editor can locate, review, and fix/replace instances without developer tools or database manipulation.
Comment #43
wim leersNW for Mayur's findings.
Comment #44
effulgentsia commentedThe original MR was merged before beta, but tracking the newly discovered failures in #42 as an RC blocker. We don't necessarily need to fix them before RC, but we should determine what the cause of the errors is and then make a determination based on that whether or not to hold up an RC on fixing them.
Comment #45
wim leersTo address TC03 + TC08, we need to do manual testing aka investigate what's happening. Any takers? 😊
Comment #46
thoward216 commentedI've had a look at TC03 & TC08 re #42 - I couldn't seem to reproduce these just based on loading the page and an error dialog appearing with the mentioned errors - I maybe misunderstanding.
But I did encounter this which I thought was related to TC03. If the code component does not have any props, if I have a component that does have props then I get the fallback as expected.
The full error I get is this error in the component settings sidebar:
Error 500: Drupal\canvas\Plugin\Canvas\ComponentSource\Fallback::buildComponentInstanceForm(): Argument #5 ($inputValues) must be of type array, null given, called in /var/www/html/web/modules/contrib/canvas/src/Form/ComponentInstanceForm.php on line 143So at this point $inputs in the ComponentInstanceForm is NULL - it looks like we account for specifically if a BlockComponent source has inputs that are NULL.
From my testing it looks like with any component source the inputs can be NULL at this point if there are no props defined, and I'm seeing what is mentioned in the code comment "the client model is invalid, because $props is the "undefined" string" so I'm not sure if that check should/needs to be so specific?
Caught up with @mayur-sose just to clarify and he showed me TC03 - it is exactly as his steps mention, I was saving the page when testing.
TC08 - we couldn't reproduce at the time but @mayur-sose will clarify those steps if he can reproduce this again.
Comment #47
thoward216 commentedI've still not be able to reproduce TC03, following the steps and discussed with @mayur-sose, both of us on the latest 1.x with this MR. He is seeing:
What I'm seeing:
So I think it maybe a good idea if someone else could also attempt to reproduce this and what they get?
Clarified steps for TC08:
- Add an SDC component to a page
- Rename the SDC component
- Clear the cache
- Reload the page
- Try to add the SDC again to the canvas
I think that this sounds like what is described at #26 and will be handled in a follow-up #3548922: A component appearing in the list of components to instantiate that is broken during development should convey that
Comment #48
tedbowI tried to test TC03. This is with component that has no props
This is what I see for a page in the editor

I am able to delete the code component by using the layers menu
I also tried it with a published page

Comment #49
tedbowTC03 with a component with props
I will look at the error if there is no props
Comment #51
tedbowFor TC08 the UI does not become unusable but there error in the php log is not very helpful
It does not contain the ID of the component that is missing
I pushed an MR that addresses TC03 and TC08
For TC08 the error message now contains the ID of the component
For TC03, it makes sure the input is an empty array not NULL so there is not an exception, but we might want to change the error message in this case. See MR comment
Comment #52
tedbowComment #53
tedbowoK chatted with mayur-sose and I know what the difference
I was doing
./vendor/bin/drush cdel canvas.js_component.NAMESo deleting the js_component config
@mayur-sose was doing
./vendor/bin/drush cdel canvas.component.js.NAMESo deleting a different thing.
I guess @mayur-sose's doing what described in the issue summary. seems like we should handle both?
Comment #54
effulgentsia commentedThanks, @tedbow! Per #44, this was tagged as "RC blocker" until we knew what the issues were. Now that we do, I'm downgrading it to "RC target". I think the current MR is great, so if other reviewers agree, let's merge it and make the RC that much better, but if reviewers spot problems with it, we don't need to hold up the RC on it.
To summarize:
componententity, not the component's source (Twig file, Block plugin, or js_component entity). I don't think that needs to be RC blocking: people aren't as likely to be deleting the Component entity during component development, they're more likely to be deleting component sources. But we should open a follow up to fix this after RC.Comment #55
tedbowAdded follow-up #3553620: Prevent or gracefully handle deletions (e.g., via Drush) of a Component config entity for #42.TC03
Comment #56
larowlanIs there any way we can write test coverage for this?
We have existing examples in core that create modules on the fly during the test - see
\Drupal\Tests\system\Functional\Theme\MaintenanceThemeUpdateRegistryTest::prepareEnvironmentwe could write a test module to$this->siteDirectory . '/modules/'that has a block plugin, and then delete it and assert the expected behaviourComment #57
nagwani commentedComment #59
mglaman@larowlan see changes to `tests/src/Kernel/ComponentInstanceFormTest.php`, it should add the necessary test coverage for a block disappearing.
Comment #62
larowlanAdded some tests that demonstrate the 500 errors I was getting in manually testing yesterday
They fail without the latest changes
This is ready for review again
Comment #63
penyaskitoRTBC. Lee to confirm all threads can be resolved.
Comment #64
effulgentsia commented@larowlan resolved all threads, so unassigning him.
Comment #65
penyaskitoI'll manually test and hopefully merge this tomorrow my morning.
Comment #66
wim leersThere's 3 parts I didn't understand at first.
JsComponent) I was able to figure out, and I left an explanation for the next person: https://git.drupalcode.org/project/canvas/-/merge_requests/220/diffs#not...Comment #67
wim leersNeeds follow-up for
Comment #68
penyaskitoI think the questions at #66 were resolved in the MR, and the tests improved to match the reality of what the query args to the component form instance.
IMHO all open MR threads should remain open for archeology purposes.
Marking as RTBC assuming all tests come green, and assigning to Wim for confirmation.
We need a follow-up for investigating
Comment #69
wim leers❤️🥳
Thanks so much for finishing this, @penyaskito! Only did some trivial refactoring to avoid repeating non-trivial logic in 3 places 👍
Comment #70
wim leersD'OH! I once again mixed this issue up with #3532514: Gracefully handle components in active development: ensure great DX, this time messing up the entire issue summary 🙈
Restored.
Comment #71
wim leersFiled the necessary follow-up: #3558721: Regression: can no longer access previously stored values for block component instances.
Comment #72
wim leersLooks like #3532514: Gracefully handle components in active development: ensure great DX having just landed has an interesting interaction with this MR, causing
JsComponentTest::testIsBroken()to fail.Comment #73
wim leersI've done plenty of manual testing while ironinig out the last problems I spotted.
RTBC per https://git.drupalcode.org/project/canvas/-/merge_requests/220#note_627277
Thanks, everyone!
Suggested commit message:
Comment #75
penyaskitoFixed! thanks everyone!
Comment #77
wim leersThis introduced consistent CI failures for the MySQL + PostgreSQL PHPUnit CI jobs:

Comment #79
wim leersComment #80
penyaskitoComment #82
wim leersComment #85
wim leersThis regressed: #3558721: Regression: can no longer access previously stored values for block component instances. E2E tests would've caught this.