[BUGFIX] Skip symbolic links instead of aborting the file listing - #1338
[BUGFIX] Skip symbolic links instead of aborting the file listing#1338CybotTM wants to merge 3 commits into
Conversation
91f8cfe to
30453af
Compare
30453af to
9b62f93
Compare
751336d to
f5e5705
Compare
f5e5705 to
3ec5ac1
Compare
|
|
|
You are right, and the missing warning was a deliberate omission I now think was the wrong call:
That required a second change: The warning is not filtered by extension. Every symbolic link under the collected directory is named. Before this branch any of them aborted the whole run, so naming all of them is strictly quieter than the old behaviour. Tests: One thing I noticed while wiring this up, unrelated to this branch: Assisted by claude-code:claude-fable-5 — Session |
|
Yes it is a good idea to move psr/log to the production requirements in a separate PR. As long as the requirements are no stricter then what symfony/http-client currently requires it can cause no breaking change but is cleaner for current updates |
|
A review of this branch turned up a build-breaking regression in the warning I added, plus a test of mine that guarded nothing. 05fb4f2 fixes both. Reproductions below, all on the same tree, base branch versus this branch. The regression. Flysystem does not merely skip a symbolic link whose target is missing, it ignores it entirely: For contrast, an unrelated warning (an unresolvable The fix. Links are now held against the very specification the listing used, instead of a second rule invented next to it. A foreign extension or an excluded path therefore stays quiet, and Flyfinder's directory pruning is mirrored via The test that guarded nothing. Every new assertion was checked by disabling its fix and watching it fail: dangling filter, traversal guard, non-descent, extension filter, exclusion filter — five for five, then restored. Full suite 835 tests 0 failures, PHPStan clean, PHPCS clean. Two things I did not fold in. The undeclared Assisted by claude-code:claude-fable-5 — Session |
05fb4f2 to
c3367cc
Compare
|
I have cut this branch back to the two-line change it started as. The diagnostics I built on top are gone: no @linawolf, your question deserves a straight answer rather than the growth it triggered. You were right that the diagnostic was wired into one path while The reason is that the pipeline already reports the case. With the warning disabled, a symlinked document referenced from a toctree produces: That names the referencing file and the entry, which my warning did not. The only case it did not cover is a symlinked document nothing references, and such a document produces no menu entry and no link either way. It also cost more than it was worth. The warning fired for links whose target does not exist — the kind a partial Two findings from that work stand on their own and are not folded back in:
Assisted by claude-code:claude-fable-5 — Session |
|
The solution as is now seems better to me. Did you check what happens if you use a symlink in an include or literalinclude? |
c3367cc to
6b6a509
Compare
|
Checked, and both work — but only with this branch. On Fixture: So The reason they are unaffected is that One consequence worth stating explicitly, since it is the flip side of the same coin: a symbolic link is no longer discovered, so a linked Assisted by claude-code:claude-fable-5 — Session |
`FlySystemAdapter::createForPath()` instantiates the Local/Flysystem adapter with the default link-handling mode, which is `DISALLOW_LINKS` in both `league/flysystem` v1 (`Adapter\Local`) and v3 (`Local\LocalFilesystemAdapter`). When the adapter's `listContents()` encounters any symbolic link during directory traversal it throws — v1: `League\Flysystem\NotSupportedException`, v3: `League\Flysystem\SymbolicLinkEncountered` — aborting the whole render. Concrete consumer impact ------------------------ `phpDocumentor\Guides\Handlers\ParseDirectoryHandler` calls `FlySystemAdapter::listContents()` to find the entrypoint of an input directory. A single symlink anywhere in the input tree kills the run, even for symlinks that point to files the parser would ignore anyway (e.g. `CLAUDE.md -> AGENTS.md` for AI tooling, vendored references, build artefacts). Downstream report in the TYPO3 render-guides wrapper: TYPO3-Documentation/render-guides#1234 Fix --- Pass `SKIP_LINKS` as the named `linkHandling` constructor argument to both the v1 and v3 Local adapters. This preserves the current "do not follow links" posture but turns an abort into a silent skip — aligning with how most documentation builders treat filesystem entries they can't or shouldn't parse. - v1: `new Local($path, LOCK_EX, Local::SKIP_LINKS)` (positional, since v1's constructor predates named args; `LOCK_EX` is the library's own default for `$writeFlags`). - v3: `new LocalFilesystemAdapter($path, linkHandling: SKIP_LINKS)` (named arg, skipping the unchanged `$visibility` and `$writeFlags`). Verification ------------ The project has `FlySystemAdapter::createForPath` as its one code path for building filesystem instances from a local path, so this covers every entry point. Downstream reproducer (now green once shipped): ln -s AGENTS.md Documentation/CLAUDE.md docker run --rm -v "$PWD:/project" -w /project \ ghcr.io/typo3-documentation/render-guides:latest \ render --config=Documentation --output=out Documentation Signed-off-by: Sebastian Mendel <info@sebastianmendel.de> Assisted-by: claude-code:claude-fable-5 Agent-Session: https://claude.ai/code/session_0114KJz3vqq2WWfx4FUdmcss Agent-Host: 0493f0
Guards the abort this fixes. With the adapters built without SKIP_LINKS the test fails with NotSupportedException on flysystem v1 and with SymbolicLinkEncountered inside UnableToListContents on v3 — the failures reported downstream. Both branches were verified that way. Signed-off-by: Sebastian Mendel <info@sebastianmendel.de> Assisted-by: claude-code:claude-fable-5 Agent-Session: https://claude.ai/code/session_0114KJz3vqq2WWfx4FUdmcss Agent-Host: 0493f0
6b6a509 to
6bd15c1
Compare
Problem
FlySystemAdapter::createForPath()builds the Local adapter with the default link-handling mode, which isDISALLOW_LINKSin bothleague/flysystemv1 and v3. Any symbolic link met duringlistContents()aborts the caller — v1 withNotSupportedException, v3 withSymbolicLinkEncounteredinsideUnableToListContents.ParseDirectoryHandleruses that listing to find the entrypoint of an input directory, so one symlink anywhere in the tree kills the whole render, including links to files the parser would never read —CLAUDE.md -> AGENTS.mdfor cross-tool AI instructions, vendored references, build artefacts. Excluding the path does not help: the listing walks it before any exclusion applies.Downstream report in the TYPO3 render-guides wrapper: TYPO3-Documentation/render-guides#1234.
Fix
SKIP_LINKSas thelinkHandlingargument on both adapters. Two lines.The stance towards symbolic links is unchanged — they are still not followed. Only the reaction changes, from aborting the run to leaving the entry out of the listing.
DISALLOW_LINKSwith the exception caught at the call sites would have been the alternative.SKIP_LINKSis narrower: it fixes every caller at once and needs no error handling inParseDirectoryHandleror in any future consumer.What a missing document still looks like
Skipping is not silent where it matters. A symlinked document referenced from a toctree is reported by the existing menu resolution, naming both the entry and the file that references it:
An earlier revision of this branch added a dedicated warning for skipped links. It was dropped: it restated what the menu already says, and it fired for links whose target does not exist — the kind a partial
composer install --no-devleaves invendor/bin— turning a passing build into a failing one for a document that was never missing.One case is not improved by this PR and is worth naming: when the index file itself is a symlink, the run still aborts, now with
Could not find an index fileinstead of the Flysystem exception. The file is sitting in plain sight, so that message is misleading. Fixing it needs the handler to explain why the file was not seen, which is a separate change.Tests
packages/filesystem/tests/unit/FlySystemAdapterTest.phplists a directory holding a regular file next to a symbolic link and asserts the regular file is listed while the link is not.The guard was checked against a reverted fix on both branches: without
SKIP_LINKSit fails withNotSupportedExceptionon flysystem v1 and withSymbolicLinkEncounteredon v3.Run locally across the full CI matrix, 5 PHP versions times lowest/locked/highest, 15 cells, 829 tests each, no failures. The
lowestcolumn resolves flysystem to 1.1.4 and exercises the v1 branch;lockedandhighestrun v3.Reproduction
ln -s AGENTS.md Documentation/CLAUDE.md docker run --rm -v "$PWD:/project" -w /project \ ghcr.io/typo3-documentation/render-guides:latest \ render --config=Documentation --output=out DocumentationFails on
main, passes with this branch.Assisted by claude-code:claude-fable-5 — Session