diagnostic_ignore_codes silently ignored for outsourced diagnostics (e.g. worse.*) · Issue #3043 · phpactor/phpactor · GitHub
Skip to content

diagnostic_ignore_codes silently ignored for outsourced diagnostics (e.g. worse.*) #3043

Description

@tborychowski

Hi.

I've had an issue with phpactor (installed in Zed via the php extension) and found a workaround (with claude's help 😉) , but this looks like a potential issue:

Summary

language_server.diagnostic_ignore_codes does not filter diagnostics produced by outsourced providers. Since language_server.diagnostic_outsource defaults to true, this means the ignore list silently fails for worse.* (worse_reflection) diagnostics — the most common ones users want to suppress.

Version

Phpactor 2025.12.21.1 (also reproducible against current master at time of writing).

Reproduction

  1. In any PHP file, define a method without a @param docblock so worse.docblock_missing_param triggers:

    <?php
    class Foo {
        public function bar(array $arr): bool { return true; }
    }
  2. Add to .phpactor.json:

    {
        "language_server.diagnostic_ignore_codes": [
            "worse.docblock_missing_param"
        ]
    }
  3. Restart the language server and open the file.

Expected: the worse.docblock_missing_param diagnostic is suppressed.
Actual: the diagnostic still appears in the editor.

Workaround

Disable outsourcing:

{
    "language_server.diagnostic_outsource": false,
    "language_server.diagnostic_ignore_codes": ["worse.docblock_missing_param"]
}

With outsourcing off, the filter works as documented.

Root cause

In lib/Extension/LanguageServer/LanguageServerExtension.php, the CodeFilteringDiagnosticProvider wrapper is only applied to providers collected for the in-process DiagnosticsEngine:

// LanguageServerExtension.php (~L550-582)
$container->register(DiagnosticsEngine::class, function (Container $container) {
    $providers = $this->collectDiagnosticProviders(
        $container,
        outsourced: $container->parameter(self::PARAM_DIAGNOSTIC_OUTSOURCE)->bool() ? false : null,
    );
    // ...
    if (count($ignoreCodes)) {
        $providers = array_map(function (DiagnosticsProvider $provider) use ($ignoreCodes) {
            return new CodeFilteringDiagnosticProvider($provider, $ignoreCodes);
        }, $providers);
    }
    return new DiagnosticsEngine(/* wrapped providers */);
});

The separately-registered AggregateDiagnosticsProvider (used by the outsourced subprocess, ~L592+) is not wrapped with CodeFilteringDiagnosticProvider, so diagnostics produced there bypass the filter entirely.

Suggested fix

Apply the CodeFilteringDiagnosticProvider (and PathExcludingDiagnosticsProvider) wrappers in both registration paths — or apply them at a layer common to both, e.g. inside the outsourced subprocess's provider collection.

Notes

  • The filter logic itself works correctly when reached — confirmed by setting diagnostic_outsource: false.
  • No log/warning is emitted to indicate the filter was skipped, making this hard to diagnose. A debug log when ignore codes are configured but a provider is outsourced would help.
  • Documentation for diagnostic_ignore_codes ("Ignore diagnostics that have the codes listed here...") doesn't mention this limitation.
    `

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions