Skip to content

fix(console): write app command loading errors to stderr - #62718

Merged
artonge merged 1 commit into
nextcloud:masterfrom
IONOS-Productivity:mk/fix/occ-console-command-load-error-to-stderr
Aug 6, 2026
Merged

fix(console): write app command loading errors to stderr#62718
artonge merged 1 commit into
nextcloud:masterfrom
IONOS-Productivity:mk/fix/occ-console-command-load-error-to-stderr

Conversation

@printminion-co

@printminion-co printminion-co commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

When an app fails to load its console commands from info.xml, OC\Console\Application::loadCommands() reports the failure with $output->writeln() — i.e. stdout — while every other diagnostic in the same method already uses $output->getErrorOutput() (insufficient memory limit, "Nextcloud is not installed", "requires upgrade", maintenance-mode notices).

The command then runs normally and exits 0, so the message silently corrupts machine-readable output:

$ ./occ app:list --output=json
Connection refused
{"enabled":{"cloud_federation_api":"1.17.0", ...},"disabled":{...}}
$ echo $?
0

Anything piping occ <cmd> --output=json into a JSON parser breaks, and there is no non-zero exit code to detect it by. --no-warnings is not a workaround, since it sets VERBOSITY_QUIET and suppresses the payload too.

$output is typed ConsoleOutputInterface, so getErrorOutput() is guaranteed by the signature. Two call sites are affected: the app_api maintenance-mode block and the regular installed-apps loop. The logger->error() calls are unchanged, so the failure is still logged with its stack trace.

How this was hit

An app with the phpredis extension loaded but no Redis configured. RedisFactory::isAvailable() only checks extension_loaded('redis'), not whether a redis config block exists, so the app's queue factory took the Redis branch, fell back to 127.0.0.1:6379 and threw RedisException: Connection refused while its console commands were being constructed. Because enable_lazy_objects defaults to true, the constructor runs inside $this->application->add($c)Command::setApplication(), so the throw surfaces outside loadCommandsFromInfoXml()'s inner catch (ContainerExceptionInterface) and lands in the outer catch (\Throwable).

The app-side behaviour is arguably its own issue; the console bootstrap should not corrupt stdout regardless of which app fails to load.

Verification

Reproduced deterministically with a throwaway app whose info.xml names a nonexistent command class:

stdout jq stderr exit
before Console command '...' is un… parse error (5) empty 0
after {"enabled":{...} ok, 21 keys the error message 0

The error remains in nextcloud.log.

On tests

No test is included. There is currently no test for lib/private/Console/*, and grep -rn getErrorOutput tests/ returns nothing, so there is no existing harness or stdout-vs-stderr assertion precedent to extend. loadCommands() also does an unstubbable require_once core/register_command.php (~138 Server::get() calls, re-fetching the real IConfig rather than an injected mock), which makes a genuine unit test a DB-group test whose result is order-dependent because require_once runs once per process.

This change adds no logic — it only redirects an existing message from one stream to the other. Happy to add a build/integration scenario (CommandLine.php already captures stdout and stderr separately) or to extract that require_once behind an injected collaborator to make the class unit-testable, if a maintainer would prefer either.

Checklist

  • Code is properly formatted
  • Signed-off-by is present (added by the contributor)
  • Tests — see "On tests" above
  • Documentation has been updated or is not required

AI disclosure

This change and this description were drafted with AI assistance (Claude Code, claude-opus-5), then reviewed by the submitting contributor. Disclosed per the Nextcloud AI Contribution Policy.

@printminion-co
printminion-co requested a review from a team as a code owner July 31, 2026 08:10
@printminion-co
printminion-co requested review from Altahrim, come-nc, provokateurin and salmart-dev and removed request for a team July 31, 2026 08:10
@printminion-co
printminion-co force-pushed the mk/fix/occ-console-command-load-error-to-stderr branch 2 times, most recently from 281a0cc to 9345ce5 Compare July 31, 2026 08:15

@artonge artonge left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Makes sense, cc @icewind1991 and @bigcat88 as original author of those lines.

@artonge artonge added bug 4. to release Ready to be released and/or waiting for tests to finish php Pull requests that update Php code labels Aug 4, 2026
@artonge

artonge commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

@printminion-co can you fix the AI policy trailer in your commit message?

@printminion-co

Copy link
Copy Markdown
Contributor Author

@printminion-co can you fix the AI policy trailer in your commit message?

@artonge Looks like it is some permission issue on your side with

Run gh api "repos/nextcloud/server/issues/62694/labels" \
gh: Resource not accessible by integration (HTTP 403)
{"message":"Resource not accessible by integration","documentation_url":"https://docs.github.com/rest/issues/labels#add-labels-to-an-issue","sta

Per copilot analysis:

Fix
Update the "Label PR as AI assisted" step (line 141-151 in .github/workflows/ai-policy.yml) to use the --method POST flag correctly and ensure proper error handling:

Current code (lines 147-151):

YAML

gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels" \
  --method POST \
  -f "labels[]=AI assisted"
echo "Added 'AI assisted' label to PR #${{ github.event.pull_request.number }}"

Recommended fix:

YAML

gh pr edit "${{ github.event.pull_request.number }}" \
  --add-label "AI assisted" || echo "Warning: Could not add label (may already exist)"
echo "Added 'AI assisted' label to PR #${{ github.event.pull_request.number }}"

The gh pr edit command is the preferred method for adding labels to PRs and has better permission handling. Alternatively, ensure the token has proper scopes by verifying that secrets.COMMAND_BOT_PAT includes public_repo or repo scope with write:repository_hooks permissions.

@printminion-co
printminion-co force-pushed the mk/fix/occ-console-command-load-error-to-stderr branch from 8e9efdc to 7a01a7e Compare August 5, 2026 13:55
When an app fails to load its commands from info.xml, the error was
written to stdout, while every other diagnostic in loadCommands() uses
$output->getErrorOutput(). The command itself then runs normally and
exits 0, so the message silently corrupts machine-readable output:

    $ ./occ app:list --output=json
    Connection refused
    {"enabled":{...},"disabled":{...}}
    $ echo $?
    0

Anything piping `occ <cmd> --output=json` into a JSON parser breaks, with
no non-zero exit code to detect it by.

Observed with notify_push on a setup that has the phpredis extension
loaded but no Redis configured: RedisFactory::isAvailable() only checks
whether the extension is loaded, so constructing the app's console
commands ends up calling pconnect() and throws RedisException.

--no-warnings is not a workaround for this, as it sets VERBOSITY_QUIET
and suppresses the payload too.

Route the message to the error output instead. It is still reported via
logger->error() exactly as before.

Assisted-by: ClaudeCode:claude-opus-5
Signed-off-by: Misha M.-Kupriyanov <kupriyanov@strato.de>
@AndyScherzinger
AndyScherzinger force-pushed the mk/fix/occ-console-command-load-error-to-stderr branch from 7a01a7e to 0cd4ece Compare August 6, 2026 09:38
@artonge
artonge merged commit dee9bd3 into nextcloud:master Aug 6, 2026
194 of 198 checks passed
@printminion-co

Copy link
Copy Markdown
Contributor Author

/backport stable33

@printminion-co

Copy link
Copy Markdown
Contributor Author

/backport to stable32

@printminion-co

Copy link
Copy Markdown
Contributor Author

/backport to stable33

@AndyScherzinger

Copy link
Copy Markdown
Member

/backport to stable34

@github-actions

Copy link
Copy Markdown
Contributor

Hello there,
Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.

We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.

Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6

Thank you for contributing to Nextcloud and we hope to hear from you soon!

(If you believe you should not receive this message, you can add yourself to the blocklist.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4. to release Ready to be released and/or waiting for tests to finish AI assisted bug feedback-requested php Pull requests that update Php code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants