Skip to content

scan --mode hosted --dry-run --vex <path> --json drops the documented vex: {skipped: true, reason: "dry_run"} marker (agent and vendored scans emit it) #744

Description

[agent] Found by the scheduled Pipenv bug-hunt routine (ledger #313).

Summary

CLI_CONTRACT.md ("Embedded VEX", the "Built from the post-run state" bullet) says: "--dry-run skips generation on every host command (nothing was changed, and a preview must not write an attestation — scan --json marks it vex: {skipped: true, reason: "dry_run"})".

scan --mode vendored and scan --mode agent emit that marker. scan --mode hosted doesn't: its JSON envelope has no vex key at all, so a dry run looks exactly like a run without --vex. Human mode is fine, since it prints Skipping VEX generation (--dry-run: nothing was rewritten). Generation is skipped correctly, and no file is written. Only the JSON marker is missing.

I found this on Pipenv projects, but the code path is shared by every hosted ecosystem. A plain requirements.txt project behaves the same way.

Impact

Low. A JSON consumer (a CI wrapper or a bot that previews scan --mode hosted --vex …) can't tell "VEX was requested but skipped for the dry run" apart from "VEX was never requested". The mod.rs comment states the marker exists so as to keep "the request visible to JSON consumers instead of silently dropping it". Since v5 scan defaults to hosted, so a bare scan --dry-run --vex … --json hits this path.

Repro (Linux, main 045d7ec, local mock of the patch API serving six 1.16.0)

mkdir app && cd app
cat > Pipfile <<'EOF'
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
six = "==1.16.0"
EOF
pipenv lock
socket-patch scan --mode hosted   --dry-run --yes --vex v.json --vex-product pkg:pypi/app@1 --json | jq '.vex, .redirect.dryRun'
#   null
#   true
socket-patch scan --mode vendored --dry-run --yes --vex v.json --vex-product pkg:pypi/app@1 --json | jq .vex
#   {"skipped": true, "reason": "dry_run"}
socket-patch scan --mode agent    --dry-run --yes --vex v.json --vex-product pkg:pypi/app@1 --json | jq .vex
#   {"skipped": true, "reason": "dry_run"}
socket-patch scan --mode hosted   --dry-run --yes --vex v.json --vex-product pkg:pypi/app@1 2>&1 | grep VEX
#   Skipping VEX generation (--dry-run: nothing was rewritten).

Expected vs actual

  • Expected (CLI_CONTRACT.md, Embedded VEX): "vex": {"skipped": true, "reason": "dry_run"} in the hosted scan --json envelope, as in the other two modes.
  • Actual: the vex key is missing. Exit 0, status: success, and redirect.dryRun: true.

Matrix (Linux, each run in a fresh project)

Project --mode hosted --mode vendored --mode agent
Pipfile.lock from Pipenv 2026.8.0 (run twice) missing marker marker
Pipfile.lock from Pipenv 2018.11.26 missing marker not run
plain requirements.txt (six==1.16.0) missing not run not run

macOS and Windows weren't probed. This is OS-independent JSON assembly.

First bad

Not bisected. The marker arrived in the agent and vendored arms with de316b4, and the hosted arm never got it.

Suspect code

  • crates/socket-patch-cli/src/commands/scan/hosted.rs:1241: VEX generation is gated on !common.dry_run, which is correct.
  • crates/socket-patch-cli/src/commands/scan/hosted.rs:1346-1360: the JSON arm sets result["vex"] only for Some(statements) or vex_error. It has no dry_run branch, unlike scan/mod.rs:368-370 and scan/vendor_flow.rs:542-543. The human arm (hosted.rs:1467) does handle the dry run.
  • The existing test (covgap_commands_scan_hosted.rs:1948, human_dry_run_prints_would_rewrite_pnpm_guidance_and_vex_skip) covers only the human output.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions