Problem/Motivation
Enabling this module breaks cron on a site that has not configured it yet — which is the state the module ships in, on purpose.
$ drush cron In ReaderFactory.php line 90: No reading service configured: set a base URL in content_credentials.settings reader.base_url.
Non-zero exit, and no cron work done — not this module's, and not the work of any module queued behind it.
Nothing is wrong with the site. The shipped default is reader.route: service with an empty base_url, deliberately, and the settings form says so in as many words: "No route is chosen for you." hook_requirements exists precisely to describe this state to a site that is in it. So the module simultaneously treats an unconfigured route as a normal condition worth reporting, and cannot survive a cron run in it.
Steps to reproduce
- Install Drupal (Standard), require
drupal/ai_disclosure:^1.0.0-alpha2and this module. drush en content_credentials.- Configure nothing.
drush cron.
No queue item, no participating bundle and no uploaded file are needed. Enabling the module is enough.
Measured on Drupal 11.4.6, PHP 8.5, ai_disclosure 1.0.0-alpha2, provemark/content-credentials v0.15.1.
Cause
The failure is at instantiation, not at reading. Core's Cron::processQueues() calls createInstance() on every queue-worker plugin definition before it claims a single item. CredentialRead::create() asks the container for content_credentials.suggester, which needs content_credentials.inspector, which needs content_credentials.reader — and that service is a factory call to ReaderFactory::get(), which builds the route eagerly and throws when there is none.
In a browser it is worse, and the fix sits behind the breakage
automated_cron is enabled by default in the Standard profile, so the exception is thrown during ordinary page requests and logged as a php Error. Measured: with automated_cron installed, saving the settings form did not take effect and the page ended in "The website encountered an unexpected error"; after drush pm:uninstall automated_cron, the identical browser interaction saved on the first attempt. A site builder following the README therefore cannot configure the module through its own form.
That last paragraph is a measured correlation, not a diagnosed mechanism — why an exception thrown at kernel.terminate prevented a form submit from persisting was not established. The cron failure is the diagnosed defect; the form symptom is evidence of its blast radius.
Why no automated gate caught it
Every queue test constructs the worker against a configured route or a test double, so the container never builds the real reader in the one state the module calls normal. composer check is green, this project's CI is green on two core minors, and the kernel suite is green on a real site — all of them configure a route first. It was found by running the module through a browser for the first time.
Proposed resolution
The reading route must not be built until a file is actually read. Two shapes are on the table and neither is chosen yet:
- the inspector takes the factory and calls
get()at read time; - the inspector keeps depending on
ReaderInterfaceand the service is declaredlazy: true, so the container hands out a proxy.
Nothing about ReaderFactory::get() itself changes: it must keep throwing. Where it is called is the whole of the fix.
Two things the fix must not do, and both will get their own test:
- It must not catch the exception somewhere. That would hide a broken route instead of deferring it.
- It must not swallow a real read failure. With an item queued and the route unusable, the failure must still be reported and the item must stay queued — an unconfigured route is a "might succeed later" condition, and discarding the item would silently lose a read.
Remaining tasks
- A test that is watched failing in exactly this state — enabled, unconfigured, empty queue — before any code changes. A test that configures a route first cannot fail for this reason.
- The fix.
- A test that the do-not-overshoot behaviour above is unchanged.
API changes
Possibly a constructor signature inside the module, depending on which shape is chosen. Nothing a site interacts with.
Data model changes
None.
Who is affected
No released site: the project has no release. Anyone who has installed 1.0.x-dev and enabled the module without configuring it has a site whose cron does not run.
Comments
Comment #6
maurice1969 commentedFixed in 414739c, on
mainand1.0.x, with a one-line follow-up in 20ceda2.CredentialInspectornow takes aReaderProviderInterface— one method returning aReaderInterface, implemented byReaderFactory— and asks for the route insideinspect(), where having a file to read is what makes a route necessary.content_credentials.readeris gone; the swap seam a site would use iscontent_credentials.reader_factory, andservices.ymlsays so where the old comment stood.The lazy-service alternative was rejected on a measurement: Drupal 11 still builds lazy services from a generated proxy class, and this service declared an interface as its class with a factory returning two different concrete readers. Whether that generator copes was never established — which is the point, since the whole subject here is when the route is built. One visible line in
inspect()beats container machinery a later reader has to know is load-bearing.Nothing about
ReaderFactory::get()changed. It still throws, and the third criterion pins that: with an item queued and no route, the read still fails and the item stays. Discarding it would silently lose work; swallowing the error would make a misconfigured site look like one reading files that carry nothing.ADR-0002 was amended rather than left stale — its sentence about depending on
ReaderInterface"and nothing narrower" is no longer literally true, while its intent is untouched.What was measured
src/change. The first one's stack carried the whole diagnosis:Cron.php:146toQueueWorkerManager::createInstance()toCredentialRead.php:108to the container toReaderFactory.php:55. A kernel test reproduced it, so no functional suite was needed.UnprocessableItemExceptionbefore it ever asked for a route — it passed without exercising the criterion at all.drush cronwith status 0 where it aborted the day before, and a node carrying a signed PNG still recordsai_generated_autonomousas pending through the full path. 136 tests, 507 assertions there.cspelljob inside it, because that job is allow_failure in the shared template.20ceda2is the word it did not know.Found by running the module through a browser for the first time.