Reproduce the exact failure once. Then check, in order: project root and start command, runtime version, dependency state, required environment-variable names, the requested port, and required background services. Do not expose secret values, kill an unidentified process, or rewrite application code before the evidence points there.
Use one diagnostic order
Randomly reinstalling packages, changing ports, and editing configuration destroys the state that could explain the failure. Keep each check narrow and record the result before taking the next action.
| Order | Question | Evidence to keep | Do not assume |
|---|---|---|---|
| 1 | Am I in the intended project root, using the documented start command? | Working directory, detected project files, exact command, first useful error | The first package.json or app.py found is the right entry point |
| 2 | Does the installed runtime match the project? | Runtime and package-manager versions; version constraints in project files | The newest runtime is automatically compatible |
| 3 | Are dependencies present and aligned with the repository? | Lockfile type, environment location, missing-module error | A forced reinstall is harmless |
| 4 | Are required variable names present? | Missing key names only, never secret values | A present but empty value is usable |
| 5 | Is the requested port already owned? | Local port, state, owning PID, process name | The process should be killed |
| 6 | Are the database, cache, or containers actually ready? | Resolved config, service status, health, focused logs | A running container is a healthy dependency |
1. Reproduce the failure without changing anything
Start from the repository's documented command. Record the current directory, the command, the exit code, and the first causal error rather than the last line of a long stack trace. Confirm the project type from its own files: package.json, pyproject.toml, requirements.txt, go.mod, Dockerfile, or compose.yaml.
A diagnostic should not delete lockfiles, databases, volumes, build caches, or user data. Preview any cleanup command and identify its target first.
2. Match the runtime and dependency method to the repository
Check the installed runtime before installing anything. For Node, compare node --version with the repository's engines, version file, or documentation. For Python, compare python --version with requires-python and check whether the active interpreter belongs to the project's virtual environment.
Let the repository choose the package manager. npm documents that an existing lockfile drives dependency resolution; Python documents virtual environments as isolated, disposable environments that should be recreated rather than copied between machines. A missing dependency tree is evidence for installation. A dependency tree that exists but was created under another runtime is evidence for a controlled recreation.
Runtime: Node 24.x required / Node 22.x observed Package manager: npm, package-lock.json present Dependency state: node_modules missing Supported next action: install with the repository's documented npm command Unsupported shortcut: delete every lockfile and try another package manager
3. Compare environment-variable names without exposing values
Compare the names in .env.example or documented configuration against the names present in the local environment. Do not paste the file, print its values, or copy it into an issue. Node's current documentation defines .env files as key-value pairs consumed as environment variables; Docker Compose has its own interpolation and precedence behavior.
- Record missing required names such as
DATABASE_URLorSESSION_SECRET. - Treat an empty required value as missing unless the project explicitly says it is optional.
- Check spelling and environment selection: development, test, and production files are not interchangeable.
- For Compose, run
docker compose configto inspect the resolved configuration without starting the stack. Review the output carefully because resolved values can be sensitive.
4. Identify the owner of the requested port
A port conflict is a fact, not permission to terminate a process. On macOS or Linux, inspect a common development port with lsof -i :3000. On Windows PowerShell, Microsoft documents filtering connections by local port:
Get-NetTCPConnection -LocalPort 3000 | Select-Object LocalAddress, LocalPort, State, OwningProcess Get-Process -Id <OwningProcess>
Once the process is identified, decide whether it is the intended application, an abandoned development server, or another service. Prefer stopping the correct development process cleanly. Change the app's port only when that is the intended configuration, not as a way to hide an unknown listener.
5. Separate service readiness from application readiness
If the app depends on Postgres, Redis, MySQL, or Docker Compose services, inspect their state before changing code. Docker's current guide recommends docker compose config for resolved configuration, docker compose up -d to start services, and focused logs or in-container checks for live debugging. A container marked running can still be unhealthy or unable to reach another service.
- Inspect service status and health first.
- Use focused logs for the failing service instead of dumping every log.
- Test the dependency through the same network path and credentials the app uses.
- Do not run
docker compose down -vas routine debugging; Docker documents that-vremoves named-volume data.
6. Know when the environment is no longer the leading hypothesis
If the documented runtime, dependencies, required key names, port, and services all check out and the same application path still fails, preserve the evidence and move inward. Inspect configuration loading, the first failing call, external integration responses, error handling, and any mock or stub path. A healthy environment narrows the fault domain; it does not prove that the build is complete.
Keep a minimal evidence record
Project root: Start command: Observed failure and exit code: Runtime and package-manager versions: Dependency and lockfile state: Missing environment-variable names (values omitted): Requested port and owning process: Required services and health: Change made: Result after one controlled retry: Next fault domain if unchanged:
This record prevents the next person or agent from repeating random fixes and makes the environment-to-code handoff explicit.
Questions
Should I change the application code when a project will not start locally?
Not first. Reproduce the failure, record the intended start command, then check the project root, runtime version, dependency state, required environment-variable names, listening ports, and required services. Change application code only after the environment is supported by evidence.
How do I check whether a port is already in use?
On macOS or Linux, inspect the port with lsof -i :PORT. On Windows PowerShell, use Get-NetTCPConnection -LocalPort PORT. Record the owning process before deciding whether to stop it or change the app's configured port.
Should I print my .env file while debugging?
No. Compare required key names against the keys that are present without copying secret values into terminals, chat logs, tickets, or screenshots. Treat empty required values as missing, but keep the values private.
When should I recreate dependencies or a Python virtual environment?
Recreate them when the runtime and package-manager evidence points to a stale, missing, or incompatible environment. Follow the repository's lockfile and documented package manager; do not use a forced reinstall as the first diagnostic step.
What if the environment checks pass but the app still fails?
Preserve the exact error and move to application-level diagnosis. Check the failing path, configuration loading, external integrations, error handling, and any stubs or fake-success behavior. A healthy environment narrows the problem; it does not prove the implementation is correct.