Troubleshooting and FAQ - ValentinNikolaev/php-upgrade-preflight GitHub Wiki
Troubleshooting and FAQ
Start by separating command failure from analysis outcome:
- Exit code
1or2: no valid report was produced; fix the invocation or environment. - Exit code
0: a valid report exists; inspectresolution.statusand the other report dimensions. resolution.status: blocked: Composer evidence found a target conflict; the command still correctly returns 0.resolution.status: unknown: evidence was insufficient or an operational problem prevented a reliable solver conclusion.
Unable to find Composer autoload.php
Run the executable installed by Composer:
vendor/bin/upgrade-intel --help
vendor\bin\upgrade-intel.bat --help
For a source checkout, run composer install at the repository root first. Do not copy the PHP executable by itself; it depends on Composer autoloading.
The shell says upgrade-intel is not recognized
Use the project-relative launcher instead of expecting a global command.
./vendor/bin/upgrade-intel --help
.\vendor\bin\upgrade-intel.bat --help
Confirm you are in the Composer project where the CLI package is installed.
Composer is missing
The analyzer launches composer from PATH unless --composer-executable selects another command.
composer --version
which composer
composer --version
Get-Command composer
External analysis needs Composer in the tools environment, not inside the target application.
The target runs PHP 7.4
Do not install the analyzer into that application. Install CLI and adapter in a separate directory running PHP 8.0+:
mkdir php-upgrade-tools && cd php-upgrade-tools
composer require php-upgrade-preflight/cli:^0.3 php-upgrade-preflight/laravel:^0.3
vendor/bin/upgrade-intel analyze \
--path=/work/php74-app \
--from-php=7.4 \
--target-php=8.1 \
--target=laravel/framework:^9.0 \
--framework=laravel
New-Item -ItemType Directory php-upgrade-tools | Out-Null
Set-Location php-upgrade-tools
composer require php-upgrade-preflight/cli:^0.3 php-upgrade-preflight/laravel:^0.3
vendor\bin\upgrade-intel.bat analyze `
--path=C:\work\php74-app `
--from-php=7.4 `
--target-php=8.1 `
--target=laravel/framework:^9.0 `
--framework=laravel
The PHP 8 analyzer can model PHP 7.4 as the source state. It does not boot the target.
Why can the CLI analyze Laravel 7 but the adapter cannot be installed there?
Host installability and analyzed target scope are separate. Project-local adapter installation requires Laravel 8โ13 and the applicable host PHP floor. External CLI analysis installs the adapter in its own PHP 8 tools project, then reads Laravel 7 metadata and source without booting Laravel 7.
Unknown command; expected analyze or wizard
Use explicit options for automation:
vendor/bin/upgrade-intel analyze --target-php=8.2
Use the guided flow only in a real terminal:
vendor/bin/upgrade-intel wizard
The Artisan command is different:
php artisan upgrade:analyze --target-php=8.2
The wizard says it requires an interactive terminal
The wizard requires both terminal-attached stdin and a visible terminal on stderr. It intentionally rejects pipes, redirected prompt streams, CI sessions, and other non-TTY contexts instead of silently accepting defaults. Use the equivalent upgrade-intel analyze --name=value command in automation.
End-of-input returns code 2. Entering cancel, quit, or q returns 130; no analysis starts in either case.
Wizard package validation is unverified
unverified means the package lookup could not prove the answer. Common causes are an empty local cache, disabled or unavailable network, authentication failure, timeout, malformed Composer metadata, or restricted execution without an isolated lookup state. It is not evidence that the package does not exist.
Choose composer.json only to avoid an external lookup, retry with the configured project repositories if their network and credential boundary is acceptable, or continue and let the actual analysis scenarios produce report evidence. An explicit not_found is reserved for a clear response from the configured repository universe; no_matching_version means the package was found but the chosen constraint matched none of its discovered versions.
No progress appears
Progress is printed only when stderr is attached to a terminal. It is deliberately suppressed when stderr is redirected or piped, while stdout remains the canonical report stream. Both upgrade-intel analyze and php artisan upgrade:analyze use durable phase lines rather than an animated spinner.
Unsupported argument at position ...
Standalone values must use =:
# Wrong
vendor/bin/upgrade-intel analyze --path /work/app --target-php 8.2
# Right
vendor/bin/upgrade-intel analyze --path=/work/app --target-php=8.2
PowerShell:
vendor\bin\upgrade-intel.bat analyze --path=C:\work\app --target-php=8.2
At least one target is required
Supply at least one of:
--target=vendor/package:constraint
--target-php=exact-version
--target-platform-profile=path
A profile containing exact PHP may provide the PHP target without a separate switch.
A PHP target is rejected
PHP simulation uses an exact value, not a Composer range.
# Accepted
--target-php=8.3
--target-php=8.3.4
# Rejected
--target-php=^8.3
--target-php='>=8.2'
If --target=php:8.3 and --target-php=8.2 appear together, they conflict and invocation returns code 2.
A package target is rejected
Use vendor/package:constraint with a valid Composer package name and constraint:
--target=laravel/framework:^11.0
Avoid spaces that the shell can split. Quote the entire token if your shell requires it.
Laravel rules are unavailable
Install the adapter beside the CLI:
composer require php-upgrade-preflight/laravel:^0.3
composer show php-upgrade-preflight/laravel
An explicit unavailable --framework=laravel is invalid input and returns code 2. An unrelated malformed adapter manifest may also emit a discovery diagnostic on stderr.
Artisan does not list upgrade:analyze
Check that the package is installed and package discovery is working:
composer show php-upgrade-preflight/laravel
php artisan package:discover
php artisan list
composer show php-upgrade-preflight/laravel
php artisan package:discover
php artisan list | Select-String upgrade
The service provider registers the command only when the application runs in console mode. If Laravel cannot boot, use the external CLI.
Artisan fails before the analyzer starts
Run:
php artisan --version
If that fails, an application/provider/configuration problem prevents every Artisan command, including preflight. External CLI analysis does not boot the application and is the safer fallback.
Laravel guidance is partial or unsupported
Read transition.framework_guidance[].uncertainties.
supported: every required hop has a rule pack.partially_supported: a covered prefix exists, then guidance stops at a gap.unsupported: no safe first hop/path can be selected.
Common causes are an ambiguous source/target major, same-major request, downgrade, target outside Laravel 7โ13, or missing required hop. Do not use resolution.status to override guidance coverage; Composer feasibility and rule coverage answer different questions.
Staged analysis was skipped
Inspect:
staged_resolution.execution_state
staged_resolution.status
staged_resolution.stop_reason
staged_resolution.evidence
Staging may skip because no adapter supplies targets, providers conflict, endpoints are ambiguous, an adjacent hop is missing, the request exceeds a budget, or no exact request-backed PHP value satisfies adapter metadata.
A skipped stage is not a Composer blocker. Direct resolution and framework guidance remain independently useful.
The command returns 0 for a blocked upgrade
This is expected.
process exit code 0 = report production succeeded
resolution.status blocked = Composer final-target evidence found blockers
CI should first require exit code 0, then parse JSON:
vendor/bin/upgrade-intel analyze --path=/work/app --target-php=8.2 --output=/work/reports/app.json
test "$?" -eq 0 || exit $?
status="$(jq -r '.resolution.status' /work/reports/app.json)"
test "$status" = feasible -o "$status" = feasible_with_changes
vendor\bin\upgrade-intel.bat analyze --path=C:\work\app --target-php=8.2 --output=C:\work\reports\app.json
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$status = (Get-Content -Raw C:\work\reports\app.json | ConvertFrom-Json).resolution.status
if ($status -notin @('feasible', 'feasible_with_changes')) { exit 10 }
Exit 10 in the wrapper is your policy, not an analyzer exit code.
resolution.status is unknown
Unknown is an honest evidence limit. Inspect scenario outcome, diagnostics, Composer execution provenance, and uncertainties.
Typical causes:
- Composer executable missing or outside the expected range;
- timeout;
- restricted cache lacks repository metadata;
- complete profile used with Composer 2.0/2.1;
- baseline validation failure;
- operational workspace problem;
- redaction or parsing prevented confident classification.
Fix the cause and rerun. Do not translate unknown into feasible or blocked.
A complete platform profile gives unknown
Check composer --version. Complete closed-world simulation needs Composer 2.2+ because older versions cannot hide all unlisted supported platform packages.
Also check for:
- contradictory target PHP between request and profile;
- contradictory extension values;
- presence-only extension assumptions combined with completeness;
- unsupported names or non-exact versions;
- malformed JSON or unsupported schema/completeness.
The analyzer does not silently downgrade complete to partial.
Windows and Linux results differ
Compare these inputs first:
- Composer version and expected range;
- compatible versus restricted mode;
- repository metadata and credentials;
- target profile digest and completeness;
- explicit extension assumptions;
- unlisted host platform packages;
- original
config.platform; - path repository content.
Stable markers normalize common absolute roots, but Composer-produced lock metadata, durations, and raw lock hashes can vary. A partial platform request is deliberately host-dependent.
The output path is rejected
--output and --save-report destinations must be outside the analyzed project. Their parent must already exist and be writable, and the destination must not be a directory. The two options cannot be combined: --output is file-only delivery, while --save-report preserves stdout and adds an identical file copy.
mkdir -p /work/reports
vendor/bin/upgrade-intel analyze --path=/work/app --target-php=8.2 --output=/work/reports/app.json
New-Item -ItemType Directory -Force C:\work\reports | Out-Null
vendor\bin\upgrade-intel.bat analyze --path=C:\work\app --target-php=8.2 --output=C:\work\reports\app.json
Avoid stdout redirection into the project because the shell creates the file before analyzer validation.
A source path is rejected
Every --source must exist and resolve inside --path. Relative values are project-relative.
--path=/work/app --source=app --source=tests/Feature
Do not pass a sibling shared library as --source; analyze it as its own Composer project or include it through the project's supported layout.
Relative Composer path repositories fail
Ordinary relative path repository URLs are resolved against the target before Composer runs. URLs containing environment variables or ~ remain untouched. Use an absolute URL or define the variable in the analyzer process.
Shareable reports replace the resolved root with [LOCAL_REPOSITORY].
Private packages cannot be downloaded
Compatible mode can use the analyzer host's normal authentication and network. Configure scoped credentials in the tools environment.
Restricted mode intentionally starts with empty Composer state and requests offline behavior. A metadata miss becomes repository_metadata_unavailable; it is not a package incompatibility.
If a credential appears unredacted, stop sharing, rotate it, and report only a synthetic reproduction through the private security channel.
A Composer scenario times out
Default scenario timeout is 300 seconds; diagnostic timeout is 60 seconds.
--composer-timeout=600 --composer-diagnostic-timeout=90
Check repository availability, credentials, proxy behavior, and noninteractive authentication. Increase timeouts only after confirming the process is making legitimate progress.
Temporary workspace cleanup failed
Default reports expose only [ANALYZER_WORKSPACE] and record cleanup_failure. If authorized, rerun with --debug to reveal and retain the exact path for diagnosis.
Do not share the debug report or retained workspace. Remove it manually after its value has been consumed, according to local retention policy.
Why does a presence-only extension still produce uncertainty?
--with-extension=ext-json proves only modeled presence through a sentinel. It cannot prove a real version satisfies a versioned constraint. Supply an exact value such as --with-extension=ext-json:8.3.0 when verified, or use a platform profile.
Why does a successful Composer candidate not mean โreadyโ?
Composer success proves one dependency solution was found under the recorded inputs. It does not prove:
- the application boots;
- framework migrations were applied;
- tests pass;
- extensions behave as expected;
- database or external service compatibility;
- deployment configuration matches the model.
Use tests, framework_findings, source_impact, and uncertainties to plan the real validation.
Why are there source inventory entries but no source impact?
source_inventory records observed declarations/usages. source_impact includes only observations correlated with a selected package change or applicable framework rule. Dynamic or unowned usages can remain inventory-only.
No impact is not a clean-runtime guarantee. Static scanning does not evaluate dynamic class names, container bindings, generated code, or application execution.
An older report consumer rejects schema 0.8
Dispatch on metadata.schema_version. v0.3.x writes 0.8, which adds required top-level composer_execution and staged_resolution, required request Composer policy, and nullable platform-profile fields.
Do not treat an absent old-schema field as equivalent to a present schema 0.8 null. Update the consumer using the ordered schema migration documentation.
FAQ for technical managers
Does feasible_with_changes approve the project?
No. It says Composer found a candidate dependency state with package changes. Engineering must review the diff, apply changes in a branch, run tests on the target runtime, and validate deployment.
Can we compare reports from two machines?
Yes, but compare recorded Composer execution and platform provenance first. A complete verified profile narrows platform differences; it does not pin repositories, credentials, network, or Composer behavior.
Can the tool estimate effort?
It reports a bounded estimate with confidence and assumptions. Use it for planning discussion, not as a fixed quote or schedule commitment.
Does restricted mode make an untrusted repository safe?
No. It hardens Composer-layer state and requests offline behavior, but it is not process or network isolation. Use an external disposable sandbox.
Should we share Markdown or JSON?
Share Markdown for human review and retain JSON as canonical evidence. Review either artifact for sensitive data before distribution.
Information to include in a bug report
- package version and
metadata.tool_versionif a report exists; metadata.schema_version;- operating system and PHP version;
- Composer version and selected execution mode;
- sanitized command with secrets and private paths removed;
- process exit code;
- relevant scenario
outcome, not only its numeric Composer exit code; - expected versus actual behavior;
- a synthetic reproducer when possible.
Never attach a retained debug workspace from a private project.