DevDiag is a Linux-first, evidence-driven diagnostic CLI for developers. It correlates repo metadata, host state, containers, services, logs, CI config, caches, GPU signals, and optional traces to explain why a project does not run correctly on the current Linux machine.
Run one command in a repo and get ranked, evidence-backed findings with safe next steps:
devdiag scan .For an interactive exploration of findings and evidence:
devdiag inspect .DevDiag is built for problems such as:
- works-on-my-machine environment drift
- Docker Compose and Dev Containers mismatch
- Podman/rootless container drift
- missing
.envkeys and Compose interpolation issues - runtime version mismatch for Node, Python, Go, .NET, Rust, and ML stacks
- port conflicts, DNS/proxy drift, and service readiness failures
- systemd, filesystem permission, UID/GID, SELinux, and AppArmor issues
- Git guardrails for tracked env files and unsafe local state
- GitHub Actions local parity,
act/local CI drift, and devcontainer-vs-CI mismatch - CUDA, NVIDIA, PyTorch, TensorFlow, JAX, and container GPU diagnostics
- package/build cache ownership and stale-cache evidence
- safe fix planning, redacted support capsules, and trace-based evidence
scan -> produce reports (automation, scripts, CI)
inspect -> interactively explore findings and evidence (read-only TUI)
check -> targeted domain checks with verbose evidence
fix -> deliberate dry-run and apply workflow outside the TUI
scanis the primary automation path. It writes nothing unless--save-reportis passed.inspectis an optional interactive workflow. It consumes the sameapp.Scanevents andschema.Reportas the CLI, but never shells out todevdiag scan.checkis for deep domain investigation (containers, CI, GPU, security, etc.).fixis separate and explicit. There is no fix-apply path inside the TUI.
Plain devdiag shows normal Cobra help and does not auto-open the TUI.
To produce reliable results with zero system disruption, DevDiag operates on a structured diagnostic pipeline:
DevDiag executes diagnostic probes sequentially down the software stack for the target project:
- OS Probe: Validates kernel, OS version, resource limits, and system variables.
- Container Engine Probe: Checks engine availability (Docker, Podman) and socket health.
- Toolkit Probe: Verifies developer tools and command-line utilities (e.g.,
crictl,git). - Runtime Probe: Confirms runtime environments (e.g., Node.js, Python, Go, CUDA).
- App Probe: Analyzes application configuration, dependencies, and port availability.
If any probe in the chain fails (e.g., crictl is missing in the Toolkit stage), DevDiag halts downstream probes, pinpoints the failure, merges the gathered evidence, and generates a detailed diagnostic finding.
How to Use:
- Run targeted checks using domain commands:
devdiag check env . devdiag check containers .
A diagnostic scan encounters a massive volume of "raw facts" (system logs, metrics, config keys, kernel messages). DevDiag passes this data through a smart filter funnel, separating irrelevant background warnings and normal messages (Noise) from stable signals that indicate real problems.
How to Use:
- Manage noise and ignore patterns in your project's
devdiag.yamlconfig file:noise: ignore_paths: - "venv/**" suppress_findings: - id: F-CI-SHELL-001 reason: "ignored local drift"
- To view hidden or low-severity findings, use the
--include-hiddenflag:devdiag scan . --include-hidden
Findings are not just simple keyword alerts. DevDiag weighs all gathered evidence (such as connection timeouts, CPU spikes, and pool saturation logs) on a scale to calculate a confidence score. If the evidence is strong enough, the finding is stamped as Actionable and returned to the developer with remediation suggestions. Speculative or weak signals are discarded.
How to Use:
- Use finding confidence to control automated build failures:
devdiag scan . --fail-severity high - Explore findings interactively (read-only) with:
devdiag inspect .
Speculative warnings ("looks odd", "maybe issue", "suspicious") that do not meet the confidence threshold are held back in a quarantine gate. This prevents false positives from cluttering reports or failing automated workflows, ensuring that only verified, actionable issues reach the developer.
How to Use:
- Configure custom severity thresholds in
devdiag.yaml:policy: fail_severity: high
- Inspect active rules and their severities:
devdiag rules list
One of the most complex works-on-my-machine problems is container GPU access. Even if a host machine has a fully functioning, visible physical GPU, a container can remain completely "blind" to it. This happens due to a "runtime gap"—usually a missing or unconfigured container runtime integration (like the nvidia-container-runtime plugin).
DevDiag bridges this gap by scanning the host driver layer, verifying container engine config, and testing GPU visibility inside the runtime context.
How to Use:
- Run GPU container diagnostics:
devdiag check containers --gpu devdiag check gpu --python
- DevDiag identifies the runtime gap and provides the exact install/connection command path to restore container GPU visibility.
Resolving diagnostic issues is a structured, step-by-step path designed to prevent system mutation until the fix is verified:
- Validate Environment: Collect evidence and verify initial state.
- Fix Configuration: Apply templates or update configs via dry-run proposals.
- Restart Runtime: Restart container engines, services, or runtimes to load changes.
- Re-scan & Verify: Run
devdiag scanto confirm the issue is fully resolved.
How to Use:
- List available fixes for your current run:
devdiag fix --list
- Interactively explore and safely preview configuration changes (dry-run):
devdiag fix --templates
For teams or projects with known, accepted configuration drift (e.g., in local development vs. CI), DevDiag supports baselining findings. Instead of cluttering the main devdiag.yaml config file, you can snapshot and manage accepted findings in a separate baseline file located at .devdiag/baseline.yaml.
Baselined findings are treated as suppressed: they are hidden from standard scan outputs, do not contribute to exit codes, and allow teams to maintain a clean-scan status while documenting deviations.
Baseline specific accepted findings by ID (recommended — each suppression is an explicit decision):
devdiag baseline create --reason "CI-only deploy secrets" --finding F-CI-ENV-001
devdiag baseline create --reason "known dev drift" --finding F-ENV-001 --finding F-DISK-001--finding is repeatable and exact-match; unknown IDs fail the command so a
typo can never silently baseline nothing. To deliberately baseline every
finding in the latest saved report, pass --all explicitly:
devdiag baseline create --reason "accepted release baseline" --allTo restrict a sweep to specific finding severities (e.g., only medium or higher):
devdiag baseline create --reason "critical bypass" --all --min-severity mediumTo set a temporary suppression expiry (supports d for days, h for hours, m for minutes):
devdiag baseline create --reason "temporary certificates drift" --all --expires 30dIf a baseline already exists, use --force to overwrite it:
devdiag baseline create --reason "updated reason" --all --forceTo specify the author of the baseline entry (defaults to $USER):
devdiag baseline create --reason "manual audit" --all --created-by "security-team"To baseline a specific run rather than the latest:
devdiag baseline create --reason "fixed reference run" --all --run-id "2026-06-06T12-00-00Z_abcd"To create a baseline with exact instance-level fingerprinting (so only finding instances with the exact same ID and symptom are suppressed, rather than silencing all findings of a given ID globally):
devdiag baseline create --reason "accepted specific instance" --all --fingerprintNote
Redaction Caveat: Fingerprints are computed from finding ID + symptom as present in the saved/redacted report. Changing redaction mode or changing diagnostic wording may require regenerating the fingerprint baseline.
List all entries, their status (active or expired), and documented reasons:
devdiag baseline list [path]Export the entire baseline file to stdout (in YAML or JSON format) or to a file (saves with private 0600 permissions):
devdiag baseline export . --format yaml --output baseline.yaml
devdiag baseline export . --format jsonImport and merge entries from a source baseline file (YAML-only) into the target baseline file:
devdiag baseline import ./team-baseline.yaml .- Automatic Discovery: During
devdiag scan ., if a.devdiag/baseline.yamlfile exists in the target directory, it is automatically loaded and applied to filter out matching active findings. - Custom Baseline Location: You can specify an explicit custom baseline path using the
--baselineflag (relative paths resolve from the current working directory). Note that this custom file must exist:devdiag scan . --baseline ./ci/baseline.yaml - Disabling Baselines: Disable baseline suppressions for scans or reports (mutually exclusive with
--baseline):devdiag scan . --no-baseline devdiag report --latest . --no-baseline
- Report Mode: When rendering from a saved report JSON directly using the
reportcommand, automatic discovery is disabled. You can explicitly pass the--baselineflag or--no-baselineto disable it:devdiag report --report .devdiag/runs/latest/report.json --baseline .devdiag/baseline.yaml
- Showing Baselined Findings: To inspect baselined/suppressed findings during a scan, use the
--include-hiddenflag:devdiag scan . --include-hidden
Integrating DevDiag diagnostics with baselines in CI/CD pipelines ensures clean builds and prevents regression of environment issues. Here are common configuration recipes for various CI environments.
- Generate or commit
.devdiag/baseline.yaml. - Run a diagnostic scan utilizing the default baseline:
devdiag scan . --baseline .devdiag/baseline.yaml
If you keep your baseline files versioned under a custom path (relative to the current working directory), target that file:
devdiag scan . --baseline ./ci/devdiag-baseline.yamlFor pre-release checks or auditing where all drift must be surfaced regardless of suppressions:
devdiag scan . --no-baseline --include-hiddenExport baseline entries to a shared file, import/merge them on other workspaces, and validate the target baseline schema:
# Export active and expired entries to a shared file
devdiag baseline export . --format yaml --output baseline.yaml
# Import and merge entries on another machine
devdiag baseline import ./team-baseline.yaml .
# Validate target baseline format and schema
devdiag baseline validate .Explicit relative --baseline paths (e.g. --baseline ./ci/devdiag-baseline.yaml) always resolve relative to the current working directory.
/usr/local/go/bin/go build -o devdiag ./cmd/devdiagThe module targets Go 1.25 as the minimum supported baseline. CI also gates Go 1.26 compatibility before release.
Install the latest stable release:
curl -fsSL -o install.sh https://raw.githubusercontent.com/meedoomostafa/devdiag/main/scripts/install.sh
bash install.shInstall a specific release:
curl -fsSL -o install.sh https://raw.githubusercontent.com/meedoomostafa/devdiag/main/scripts/install.sh
DEVDIAG_INSTALL_VERSION=v0.2.7 bash install.shThe installer supports Linux distributions with Bash, tar, curl or wget,
and Go 1.25 or newer. It installs to /usr/local/bin when writable and falls
back to ~/.local/bin otherwise.
Useful installer options:
# Install to a user-local directory and configure PATH automatically.
curl -fsSL -o install.sh https://raw.githubusercontent.com/meedoomostafa/devdiag/main/scripts/install.sh
bash install.sh --bin-dir "$HOME/.local/bin" --add-to-path
# Preview the resolved release tag, archive URL, and local paths without mutating files.
curl -fsSL -o install.sh https://raw.githubusercontent.com/meedoomostafa/devdiag/main/scripts/install.sh
bash install.sh --dry-run
# Print manual PATH addition command instead of modifying profiles.
bash install.sh --print-path-commandFor private repository installs, pass an authenticated GitHub token so the installer can fetch the source archive:
curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" -o install.sh \
https://raw.githubusercontent.com/meedoomostafa/devdiag/main/scripts/install.sh
GITHUB_TOKEN="$GITHUB_TOKEN" bash install.shThe installed DevDiag binary is a single Go binary built with CGO disabled. The installer itself requires Bash, tar, mktemp, Go 1.25+, and curl or wget. DevDiag may optionally call system tools such as Docker, Podman, kubectl, strace, systemctl, git, nvidia-smi, and language runtimes depending on the checks requested.
To update DevDiag, run the updater. It resolves the latest GitHub Release,
downloads the platform binary asset, verifies it against the release
checksums.txt and its signed SLSA provenance attestation (via the
GitHub CLI — gh must be on PATH, or the update is refused), then swaps
the binary atomically, keeping a devdiag.old rollback backup. No
downloaded scripts are ever executed.
devdiag updateTo preview the update plan without mutating anything:
devdiag update --dry-runFor manual PATH configuration:
export PATH="$HOME/.local/bin:$PATH"Fish:
fish_add_path "$HOME/.local/bin"devdiag doctor self
devdiag scan . --format human
devdiag scan . --format json
devdiag scan . --format ndjson
devdiag scan . --ci
devdiag scan . --verbose
devdiag scan . --include-hidden
devdiag scan . --save-report
devdiag inspect .
devdiag tui . # alias for inspect
devdiag check env . --verbose
devdiag check runtimes . --verbose
devdiag check containers . --verbose
devdiag check containers --gpu
devdiag check security . --verbose
devdiag check ci .
devdiag check gpu --python
devdiag check cache .
devdiag repro -- npm test
devdiag repro --format ndjson -- npm test
devdiag trace --scope file,process,network -- npm test
devdiag trace --backend ebpf --scope file,process,network -- npm test
devdiag fix --templates
devdiag fix --list
devdiag capsule create
devdiag capsule inspect support-run.devdiag.tgz --format json
devdiag rules list --format json
devdiag rules packs --format json
devdiag rules validate team-rules.yaml --format json
devdiag issue template --run-id <run-id> --format markdowndevdiag scan is non-mutating by default. Commands that load saved reports,
such as devdiag fix --list, require a prior devdiag scan --save-report.
DevDiag prefers a shareable devdiag.yaml file for team baselines and policy
settings. Legacy .devdiag.yml and .devdiag.yaml files are still read for
compatibility. The .devdiag/ directory remains reserved for local run
artifacts and should not be used as the shareable team config.
policy:
fail_severity: high
ci:
env:
ignore_missing_local:
- CI_ONLY_SECRET
ignore_missing_ci:
- LOCAL_ONLY_DEVELOPMENT_KEY
noise:
ignore_paths:
- ".venv/**"
- "venv/**"
- "**/site-packages/**"
suppress_findings:
- id: F-CI-SHELL-001
reason: "local shell intentionally differs from CI"ignore_missing_local suppresses F-CI-ENV-001 for CI variables that should
not appear in local env files. ignore_missing_ci suppresses F-CI-ENV-002 for
local-only variables that should not appear in CI.
policy.fail_severity sets the default findings exit-code threshold for
scan and check commands when --fail-severity is not passed. Explicit CLI
flags always win, and invalid config values are reported as partial collector
results.
Some findings tier their severity by context. F-DOCKER-001 (Docker daemon
inactive) is high when the repo demonstrably runs containers (Compose files
or a devcontainer) or when you explicitly run devdiag check containers, but
only medium for Dockerfile-only repos, where the image is typically a
build/deploy artifact and a stopped local daemon should not fail a
--fail-severity high CI gate.
By default, scan and inspect keep the screen focused on actionable medium,
high, and critical findings. Low-severity, informational, evidence-only, and
configured suppressed findings are hidden from rendered and saved reports unless
--include-hidden is passed. noise.ignore_paths keeps generated or dependency
trees from becoming project-level evidence, and noise.suppress_findings hides
known project-specific noise.
Team rule-pack metadata can be inspected locally before it is shared:
devdiag config validate devdiag.yaml --format json
devdiag rules packs --format json
devdiag rules validate team-rules.yaml --format json
devdiag scan . --rule-pack team-rules.yaml --format jsonRule packs default to engine: go for built-in metadata. External
engine: rego packs must declare entrypoint and policy_files; policies
receive the normalized scan snapshot and may only return finding candidates.
Saved runs can generate issue-ready handoff text and optional capsule metadata:
devdiag scan . --save-report
devdiag capsule create --run-id <run-id>
devdiag issue template --run-id <run-id> --capsule support-<run-id>.devdiag.tgz --format json
devdiag team bundle --run-id <run-id> --format jsonteam bundle is a local export surface for future hosted dashboards or editor
extensions. It includes saved report metadata, optional capsule metadata,
built-in rule-pack metadata, stable output names, documented exit codes, and a
redacted issue-template body.
When sharing diagnostic results with teammates, platforms, or helpdesks, DevDiag bundles the unique Run ID, redacted logs, environment and configuration summaries, and remediation notes into a single, self-contained, and reproducible support capsule (a compressed .tgz archive).
How to Use:
- Save your scan and package it into a support capsule:
devdiag scan . --save-report devdiag capsule create --run-id <run-id>
- Inspect capsule contents without extraction:
devdiag capsule inspect support-run.devdiag.tgz --format json
Remote dry-run examples:
devdiag remote doctor user@host --dry-run
devdiag remote sync user@host --dry-run --profile minimal
devdiag remote enter user@host --dry-run --format json
devdiag remote clean user@host --dry-run
devdiag remote doctor k8s:default/api-pod --dry-run --format json
devdiag remote sync k8s:prod/default/api-pod --k8s-container app --dry-run --format json
devdiag agent explain F-PORT-001 --format json
devdiag agent run -- npm test
devdiag agent sandbox --patch fix.patch -- npm testSSH remote commands also accept explicit OpenSSH client options for release verification or CI-provisioned targets:
devdiag remote doctor user@host \
--ssh-identity-file /path/to/key \
--ssh-known-hosts-file /path/to/known_hosts \
--ssh-strict-host-key-checking yesKubernetes remote commands use kubectl exec with targets in
k8s:namespace/pod or k8s:context/namespace/pod form. Multi-container pods
can be selected with --k8s-container <name>. Remote files are staged under
/tmp/devdiag-remote/<session> and remain manifest-cleanable through
devdiag remote clean.
Supported output formats:
human, json, ndjson, markdown, github
Exit codes:
0 success
1 high or critical findings exist
2 invalid user input
3 collector partial failure
4 permission denied
5 unsafe operation refused
6 command reproduction failed
7 trace unavailable
8 internal error
An unavailable optional collector is reported as evidence but does not fail a scan by itself. Partial, timeout, permission-denied, and failed collectors use the documented nonzero exits.
DevDiag is local-first and non-mutating by default.
- Collectors do not mutate system state.
inspectis read-only. It does not apply fixes, edit files, restart services, or mutate containers, hosts, or remote targets.- Redaction is enabled by default.
- No upload happens by default.
- Support capsules are local files unless the user shares them.
- Repro command args, stdout/stderr previews, command logs, and capsule entries are redacted before persistence by default.
- Capsules include
report.md,findings.json, snapshot JSON, redacted command logs when available, andredaction/rules-applied.json. - Fixes render dry-run proposals unless explicitly applied.
- Guarded fixes require an interactive TTY.
- Guarded fix templates expose risk text and rollback metadata when available.
- Broad destructive commands and unsafe policy changes are blocked or manual-only.
- External repo files, logs, package metadata, and web text are untrusted data, not instructions.
To ensure privacy and safety in public or team-shared spaces, DevDiag draws a strict "redaction curtain" over sensitive host details. It masks private data like host IPs, usernames, absolute paths, access tokens, credentials, hostnames, and session IDs. Only the safe report metadata (findings, impact, remediation, summary, and confidence) is kept visible.
How to Use:
- Redaction runs automatically on all commands, outputs, and support capsules.
- Inspect the redaction rules that were applied during a scan by checking the
redaction/rules-applied.jsonfile inside your generated capsule.
Use writable Go caches in restricted environments:
PATH=/usr/local/go/bin:$PATH \
GOCACHE=/tmp/devdiag-go-build \
GOMODCACHE=/tmp/devdiag-go-mod \
XDG_CACHE_HOME=/tmp/devdiag-cache \
/usr/local/go/bin/go test ./...Additional checks:
/usr/local/go/bin/go vet ./...
git diff --checkWhen running in a TTY:
devdiag inspect .
devdiag tui .When running without a TTY (should fail cleanly with exit code 2):
devdiag inspect . < /dev/null
devdiag tui . < /dev/nullAfter using inspect, confirm scan output remains unchanged:
devdiag scan . --format json --fail-severity off
devdiag scan . --format ndjson --fail-severity offDevDiag includes a robust composite GitHub Action (action.yml) that runs diagnostic scans directly inside your CI pipelines.
- Auto-Provisioning: Automatically installs Go (using
actions/setup-go@v5) and buildsdevdiagfrom the action's source code if a binary is not preinstalled in the runner's PATH. - Saved-Report Rendering: In the default
save-report: truemode, the action runsdevdiag scanonce to save a JSON report, then renders the saved report viadevdiag reportfor the requested output format. Withsave-report: false, it renders directly fromdevdiag scan. - Secret Masking: Automatically registers sensitive inputs with GitHub's mask command (
::add-mask::) to prevent credential leakage in logs.
To run the DevDiag action against your checked-out repository, ensure your workflow defines the minimum read permissions:
permissions:
contents: read| Input | Description | Default |
|---|---|---|
path |
Path to scan | . |
profile |
Diagnostic profile (e.g., ai-ml) |
"" |
rule-pack |
Path to an external deterministic rule pack | "" |
redact |
Redaction level: default, strict, off |
default |
ci |
Force CI/local parity collection and evaluation | true |
include-hidden |
Include low/info and configured hidden findings | false |
save-report |
Save report and upload as JSON artifact | true |
summary |
Write a short GitHub job summary | true |
fail-severity |
Minimum finding severity that fails the action: off, info, low, medium, high, critical |
high |
artifact-name |
Name for the uploaded JSON report artifact | devdiag-report |
format |
Output format: github (for inline annotations), human, json, ndjson, markdown |
github |
mask-values |
Newline-separated values to mask | "" |
use-system-devdiag |
Prefer using a preinstalled devdiag binary in PATH | false |
| Output | Description |
|---|---|
report-path |
Path to the generated JSON report artifact on the runner |
summary-written |
Whether the action wrote a GitHub job summary (true/false) |
scan-exit-code |
The raw exit code of the devdiag scan command |
render-exit-code |
The exit code of the devdiag report command (present only when save-report is true) |
report-uploaded |
Whether the JSON report was uploaded as an artifact (true/false) |
The GitHub Action runs under one of two modes depending on the save-report setting:
save-report: true(Default): Runsdevdiag scan --format json --save-reportonce to perform diagnostics and save a JSON report. It then copies the saved report to the artifact path for upload, and callsdevdiag reportto render the saved report in the requested output format.render-exit-codeis populated.save-report: false: Runsdevdiag scandirectly with the requested output format. No JSON report is saved or uploaded, andrender-exit-codeis empty.
Always check out your repository code before executing the DevDiag scan:
- uses: actions/checkout@v4Version pinning: reference the action by major tag (@v0), which moves
only after a release fully publishes (binaries, checksums, provenance
attestation). For immutable pinning, use the full commit SHA of a release and
let Dependabot/Renovate propose bumps:
- uses: meedoomostafa/devdiag@v0 # tracks latest v0.x release
# or
- uses: meedoomostafa/devdiag@<full-sha> # immutableA latest alias also exists for convenience; the release pipeline moves it
together with v0, only after a release fully publishes. main is not
recommended for CI: it is unreleased code with no publication guarantees.
For anything security-sensitive, pin the full commit SHA.
Emits workflow annotations for findings and fails the build if high or critical issues are found:
- name: Run DevDiag Scan
uses: meedoomostafa/devdiag@v0Generates annotations and uploads artifacts without failing the build, allowing you to review environment diagnostics offline:
- name: Run DevDiag Scan (Non-Blocking)
uses: meedoomostafa/devdiag@v0
with:
fail-severity: offChecks for version drift and environment matches between your local container configs and GitHub Actions variables:
- name: Run DevDiag CI Parity Scan
uses: meedoomostafa/devdiag@v0
with:
ci: 'true'
fail-severity: medium4. Include Hidden Findings
Includes low-severity and informational rules in the final artifact report for thorough security auditing:
- name: Run Detailed DevDiag Scan
uses: meedoomostafa/devdiag@v0
with:
include-hidden: 'true'
artifact-name: devdiag-audit-report







