Skip to content

Print one JSON envelope from every command (v5.0) - #1369

Merged
Mikola Lysenko (mikolalysenko) merged 40 commits into
mainfrom
v5/json-envelope-unify
Oct 10, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 40 commits into
mainfrom
v5/json-envelope-unify

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Every command's --json output now uses the one envelope shape for v5.0. scan, get and rollback were the last commands printing their own pre-v3 shapes. This finishes the migration that CLI_CONTRACT.md listed as pending. This is a breaking change on purpose (v5.0, MAJOR).

Stacked on #1273 (one GC shape and rollback counters); merge that first.

Closes #931. Closes #1062.

What every command now prints

  • command, status, dryRun, events, summary, warnings, plus error: {code, message} on failure, and gc when a sweep ran.
  • status comes from one camelCase set: success, partialFailure, error, noManifest, paidRequired, notFound, notInstalled, noMatch, noPackages, selectionRequired. No snake_case statuses or actions remain.
  • Every per-package outcome is an event, recorded so that summary always equals the event counts. New action: rolledBack.
  • Events from the vendored and hosted legs carry details.mode: "vendored" | "hosted", the convention list already used.
  • Warnings are always top-level {code, detail}. There are no string warnings and no nested *.warnings arrays.
  • Usage, lock and early errors also print a full envelope.
  • Each command's own data stays as top-level keys beside the shared ones, through Envelope::extra. Examples: scan's packages, rollout, policy and redirectState; rollback's paths and hosted.editedFiles.

Per command

  • rollback: rolledBack, alreadyOriginal, failed, results[], vendored*, manifest and hosted.{reverted,failed,unsupported} became events:

    • a restored copy is rolledBack
    • an already-original copy is skipped already_original
    • a not-installed package is skipped package_not_installed
    • a failure is failed with a specific code (hash_mismatch, vendor_revert_kept, hosted_restore_refused, …)
    • a removed manifest entry is removed with details.manifest: true
    • a dry run reports its previews as verified

    gc: {skipped: true} became an absent gc plus a gc_skipped warning. Exit codes are unchanged.

  • scan / get:

    • apply.patches, download, the nested vendor envelope and redirect.patches all merged into events.
    • The count keys (found, totalPatches, redirected, …) are replaced by summary.
    • would_add/would_pin/would_vendor became verified. already_vendored and dry-run refusals became skipped with a code.
    • options[].published_at became publishedAt.
    • Removed --prune manifest entries are removed events, and the sweep is the envelope's gc.
  • Corrupt manifest (An unparseable .socket/manifest.json is reported under five different --json error codes depending on the command #931): one mapping, manifest_load_error, on every command. An unparseable manifest is manifest_invalid; an unreadable one is manifest_unreadable. This replaces apply_failed, repair_failed, the undocumented invalid_manifest, and the manifest_unreadable that apply --check / vendor --check / vex / get / rollback used for an unparseable file. Agent-mode scan now fails on an unloadable manifest. The other scan modes add a warning with the same code.

  • remove:

    • The GC event's details are {count, checked}, matching repair's.
    • Each restored installed copy is its own rolledBack event (it was details.rolledBack).
    • Sweep failures appear as cleanup_failed warnings.
  • Sidecars: SidecarFileAction / SidecarSeverity serialize camelCase. Every value is one word, so the output bytes don't change.

Bugs fixed on the way

Contract

  • The CLI_CONTRACT.md envelope, event, action matrix, error-code tables, rollback contract, scan/get JSON section and jq recipes all describe the new shape.
  • "Migration status" now lists every command as ✅.
  • The legacy helpers (legacy_error, set_error, is_legacy_shape, …) are deleted.

Tests

  • cargo test -p socket-patch-cli -p socket-patch-core --tests --lib --no-fail-fast (local, macOS arm64): 13,307 passed, 2 failed. The 2 failures are e2e_vendor_cargo_build cases that need x86 cargo toolchains, which don't run on this machine ("Bad CPU type"). They are unrelated to this change.
  • New tests:
  • E2E suites gated on toolchains or production were updated by reading them and still need CI: e2e_scan, e2e_npm, e2e_redirect_*_build, e2e_vendored_production, e2e_vex_*, e2e_bun_lockb, e2e_hosted_production, and others.
  • cargo clippy --locked --workspace --all-features -- -D warnings and cargo fmt --all -- --check are clean.
  • CHANGELOG.md is untouched, per AGENTS.md.

🤖 Generated with Claude Code


Note

High Risk
Breaking v5.0 JSON contract for automation consuming CLI output, plus broad changes to get/scan/apply error and manifest paths that affect exit codes and failure reporting.

Overview
Completes the v5.0 breaking migration so every command emits a single shared JSON envelope (command, status, events, summary, structured warnings, optional error) instead of legacy per-command shapes. get, scan, and rollback paths now record PatchEvent outcomes (e.g. downloaded, applied, failed, skipped) and derive counts from summary; the agent download engine writes into a caller-owned Envelope and no longer prints JSON itself (fixes double-document output on lock/manifest errors).

Manifest handling is unified via manifest_load_error (manifest_invalid vs manifest_unreadable) across apply, get, vendor, list, and related flows, replacing ad hoc codes like apply_failed for bad manifests. Nested apply failures fold through record_apply_outcome instead of mutating patches[] JSON.

CI switches Rust installs from rustup show to scripts/rustup-retry.sh toolchain install (retries download blips; explicit install). vlt-compatibility adds a changes gate (vlt-compat-gate.py) to skip the heavy matrix when only unrelated ci.yml edits triggered the workflow. Bun lockb e2e adds a matrix filter for binary_shared_bundled_record_hosted_pin_is_managed.

Reviewed by Cursor Bugbot for commit 48c63c6. Configure here.

, #1066)

GC was reported four ways. repair and remove buried the sweep in
artifact-level event details with no byte count, while rollback and
scan --prune printed a hand-built `gc` object. The contract documented
summary.bytesFreed, summary.bytesDownloaded and events[].bytes, but no
command emitted any of them, so its GC jq recipe returned null.

- json_envelope::GcReport {removedBlobs, removedDiffArchives,
  removedPackageArchives, bytesFreed} is built from the three sweep
  passes and serialized identically everywhere: the envelope's new
  top-level `gc` (repair, remove), rollback's `gc` and scan's `gc`. The
  hand-written json! blocks are gone.
- summary.bytesFreed is always present and mirrors gc.bytesFreed.
  events[].bytes is set on the GC carrier event and on --update's
  downloaded event. summary.bytesDownloaded is dropped from the contract.
- repair's GC carrier event no longer bumps summary.removed/verified,
  matching remove: summary counters count patch entries, and the sweep
  totals live in `gc`.
- remove's human output now names the diff/package archives it sweeps.
- rollback --json: rolledBack and failed now span the agent, vendored
  and hosted legs (#1066). A run where something failed and nothing was
  rolled back, already original or not installed now reports
  status "error" with error.code rollback_failed instead of
  partial_failure. Exit codes are unchanged.
- CLI_CONTRACT.md: the envelope and PatchEvent schemas, the PatchAction
  "Emitted by" column, the per-command action matrix and the GC jq
  recipe now match the emitters. New unit tests pin the documented
  summary/gc/PatchEvent key sets against what json_envelope serializes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The --dry-run preview of scan --prune/--sync used its own vocabulary
(prunableManifestEntries, orphanBlobs, orphanDiffArchives,
orphanPackageArchives, revertableVendoredEntries, vendorOrphanDirs,
bytesReclaimable). It now prints the same keys as the wet pass and every
other GC-running command, counting what the pass would remove, and leaves
out only the keys a real pass alone can fill (keptVendoredEntries,
failedVendoredEntries, skipped, warnings). v5.0 MAJOR.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Envelope::extra (flattened) carries a command's own payload keys
  beside the shared ones; set_extra refuses a shared key.
- Status gains notInstalled, noMatch, noPackages and selectionRequired
  (get's outcomes) so no command needs a snake_case status.
- PatchAction gains rolledBack; summary.rebuilt and summary.rolledBack
  are always present.
- manifest_load_error is the one mapping from a manifest load failure to
  manifest_invalid / manifest_unreadable (#931); list and remove use it,
  and remove reports a manifest that vanished mid-run as
  manifest_not_found instead of manifest_invalid.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every rollback outcome is now a PatchEvent: agent restores are rolledBack
(verified on a dry run, installed copy in details.path), already-original
and not-installed entries are skipped with already_original /
package_not_installed, failures are failed with the blocking file's code.
Vendored and hosted legs carry details.mode; drift-keeps are failed
vendor_revert_kept (they still exit 1). Manifest entries the run drops are
removed (verified on a dry run) with details.manifest. GC goes through
set_gc; a requested GC that could not run adds the gc_skipped warning.
Every error path prints a full envelope; manifest load failures use
manifest_load_error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#931)

apply, apply --check, repair, vendor, vendor --check and vex now report an
unparseable manifest as manifest_invalid and an unreadable one as
manifest_unreadable through json_envelope::manifest_load_error, like list
and remove. Gone: apply_failed / repair_failed for a manifest load
failure, and vendor's undocumented invalid_manifest. vex keeps exit 2.
apply's run_locked reports a manifest that vanished mid-run as
manifest_not_found. Exit codes are unchanged.

remove's GC carrier now matches repair's (details.count + details.checked,
bytes); the per-kind totals are only in gc. The in-place rollback leg is
reported as rolledBack events (summary.rolledBack) instead of the carrier's
details.rolledBack. Sweep failures reach --json as cleanup_failed
warnings. Vendored- and hosted-leg events of remove and repair carry
details.mode; repair's download carrier key is now details.downloadMode.

Sidecar file actions and severities serialize with the envelope's
camelCase enum convention (wire-identical for today's one-word values).

Adds a regression matrix test over list, remove, apply, apply --check,
repair, vendor, vendor --check and vex.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds tests/common/rollback_json.rs, per-leg views projected from events,
and moves the rollback-centric suites onto summary/events.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Covers the mixed suites (apply multicopy, dispatch, global scope, json
error shape, hosted/vendored takeovers) and the ignored e2e suites, which
were fixed by reading.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CLI_CONTRACT.md: manifest_invalid / manifest_unreadable now list every
command that loads the manifest (v5.0, MAJOR, #931; vex keeps exit 2);
the top-level error table gains the codes apply, remove, vendor, vex and
--update already emitted; the remove/repair matrix rows, the rolledBack
action row and new notes describe the shared GC carrier (details.count +
checked), details.mode on vendored/hosted-leg events, repair's
details.downloadMode, and the sidecars[] value-tag casing.

apply --check prints a noManifest envelope when the manifest vanishes
between the existence probe and the read, instead of printing nothing.
Tests pin details.mode on remove's hosted/vendored legs and repair's
vendored phase.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
All packages refused and nothing rolled back is a failed run since #1066.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The rollback contract's JSON section now maps each outcome to its event,
the action matrix and migration status list rollback as an envelope
command, and the errorCode / EnvelopeError tables carry its codes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
scan and get now print one serialized Envelope per run (command,
status from the Status enum, dryRun, events, summary, warnings, gc,
vex), built by one emitter each (scan::emit_scan, get::emit):

- The agent download engine records per-patch events into the caller's
  envelope (downloaded / updated + oldUuid with the patch metadata in
  details, skipped already_in_manifest, failed with download_failed /
  patch_no_applicable_files / blob_write_failed or the refusal code),
  then applied / failed events for the nested apply; it never prints,
  so scan --mode agent --json can no longer put two JSON documents on
  stdout when the lock is held or the manifest is unreadable.
- Hosted runs record applied (verified on --dry-run) / skipped events
  tagged details.mode hosted; redirect shrinks to {mode, rewrittenFiles}
  and its warnings move to the top level.
- Vendored runs merge the vendor engine's events, warnings and sidecars
  into the outer envelope (details.mode vendored) instead of nesting a
  vendor envelope; the dry-run preview becomes verified / skipped events.
- scan --prune records pruned manifest entries as removed events
  (details.manifest), reverted vendored entries as removed, drift-kept
  and failed reverts as skipped, the sweep as the envelope's gc, and a
  pass that could not take the lock as a gc_skipped warning.
- get's statuses become notFound / noPackages / noMatch / notInstalled /
  paidRequired / selectionRequired; selection_required options use
  publishedAt; every error prints a full envelope.
- Exit parity (#1062): one fetch_details decision for both outputs, no
  human-only fetched == 0 failure, hosted --json uses the policy-gated
  prune, and the human vendored dry run previews the --prune GC.
- A manifest that exists but cannot be loaded fails agent-mode scan with
  manifest_invalid / manifest_unreadable and is a warning elsewhere.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CLI_CONTRACT.md: scan and get join the envelope (migration status,
action matrix, PatchAction rows, error codes, a new "scan and get JSON"
section replacing the patches[] entry shape, jq recipes), and the scan
mode / get paragraphs describe events, the redirect payload and top-level
warnings instead of the nested blocks.

Tests: shared envelope invariants in tests/common/envelope.rs, a legacy
redirect rebuild for the in-memory engine parity harness, scan envelope
tests (agent events, dry run, lock held prints one document, manifest
load errors) and the #1062 human/JSON parity tests, get envelope tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every test that read the legacy scan/get JSON (top-level counters,
patches[], apply / download / vendor / redirect blocks, nested and string
warnings, snake_case statuses and actions, gc sub-keys) now reads the
envelope: events (details.mode for the hosted and vendored legs), summary,
top-level warnings, redirect.rewrittenFiles and the scan payload keys.
The in-memory hosted engine parity tests compare the engine's redirect
block against one rebuilt from the disk run's envelope. Env-gated e2e
files were updated by reading.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every command now prints the envelope, so the legacy error helpers
(legacy_error, set_error, is_legacy_shape, …) are deleted and every usage
error is a full envelope.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Assisted-by: Claude Code:claude-opus-5-5
The shared bun.lockb rollback helper expected status "error" for every
caller, so the hosted -> vendored -> hosted takeover leg went red: there
a manifest record makes the agent leg report the copy already original,
and the run is correctly a partial_failure. A branch rewrite had dropped
the earlier fix for this. Each caller now states which outcome it
expects, so the takeover leg checks partial_failure and the hosted-only
alias/transitive shapes keep checking rollback_failed.

Assisted-by: Claude Code:claude-opus-5-5
Comment thread crates/socket-patch-cli/src/commands/scan/vendor_flow.rs Outdated
Resolve against main's removal of the diff download path (#1049) and
the restore-blob GC fix (#1316): repair drops the created-file blob
pass and keeps the GcReport carrier; remove keeps the archive noun
loop; the contract keeps "update" and drops the removed paidRequired
status; the envelope contract test uses AppliedVia::Blob.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
main's new test read gc.prunableManifestEntries, which this branch
renamed to prunedManifestEntries for the dry-run preview.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings in main through #1277 via #1273. Resolutions:
- get keeps the envelope's paidRequired status and drops main's legacy
  {"status": "paid_required"} emitter; contract_paid_required.rs now pins
  the envelope row instead of the legacy one.
- repair: main removed the diff download path, so the created-file blob
  pass is gone; the download event keeps details.downloadMode, now
  always "file".
- scan: the envelope arms read main's lockfile_only_count; main's
  hoisted release-variant narrowing replaces the hosted-only copy in get.
- CLI_CONTRACT.md: three-way merged per paragraph; main's new hosted
  warning rows point at the top-level warnings[] like their neighbours.
- tests: main's new tests (cargo takeover refusal, #1127 human prune,
  bun.lockb already-original rollback) read the envelope shapes.
- json_envelope contract tests normalize CRLF so they pass on a Windows
  checkout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The envelope retired scan's legacy counters and nested blocks, but the
ignored e2e suites, the Python compatibility harnesses and the scan
benchmark still read them, so every compat workflow failed.

- e2e (hosted production, gem, vlt, bun.lockb, pypi/yarn hosted): hosted
  pins are the details.mode "hosted" applied/verified events, skips the
  hosted skipped events, warnings the top-level warnings[] (omitted when
  empty), prune reverts the vendor_reverted events, hosted rollback
  restores rolledBack events.
- scripts/backtest-{bun,uv,vlt,pdm,pipenv,poetry}.py: per-leg event
  counts replace redirect.redirected / vendor.summary.applied /
  apply.applied, and codes come from error.code, event errorCodes and
  warnings[]; the harness unit tests use envelope fixtures.
- socket-patch-bench: validation accepts both shapes, since the base and
  head binaries straddle the change (packagesWithPatches and
  totalPatches derive from packages[], redirected from the hosted
  events).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 10, 2026
….lockb e2e

- json_envelope's contract tests read CLI_CONTRACT.md through one
  LF-normalized copy, so a Windows checkout (CRLF) no longer misses the
  ```jsonc fence (review thread on #1273; full-scope test windows).
- e2e_bun_lockb's shared-bundled case also has an agent copy that fails
  (hash_mismatch on a bundled copy the patch never touched); with #1066's
  all-leg counter, `failed` is the hosted refusal plus those agent failures.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Takes #1273's LF-normalized contract_doc(); the bun.lockb shared-bundled
rollback counts agent failed events (no details.mode) on top of the
refused hosted pin.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The vendor engine marks a hard error and still returns Ok for an
unreadable vendor ledger, a refused group commit and
vendor_commit_failed. merge_vendor_envelope dropped venv.error, so a
corrupt .socket/vendor/state.json under scan/get --mode vendored --json
printed a bare partialFailure with no code. The merge now marks the
outer envelope with the engine's {code, message}; a caller aborting
with its own error still overrides it. Unit tests pin both arms.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Resolve remove.rs imports: keep main's KeepCause and this branch's GcReport.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings in main's latest via #1273. Conflicts:
- CLI_CONTRACT.md: keep the envelope wording and port main's additions
  (cargo_build_cache_stale, cargo_cache_patch_kept, the Cargo shared-cache
  exemption in GC and rollback, redirect_cargo_dep_overridden,
  vendor_pypi_reinstall_required, vendor_revert_residual_reference, the
  pnpm keyless-yaml restore) onto it; GC warnings are top-level
  warnings[], a kept entry gets no preview event, and rollback's residual
  keep is its failed vendor_revert_kept event.
- rollback.rs tests: keep both the envelope tests and the PyPI reinstall
  note test.

Port main's new tests to the envelope: e2e_cargo reads pruned manifest
entries from details.manifest events and cargo_cache_patch_kept from the
top-level warnings; in_process_redirect and mode_migration_pypi count
hosted pins from events and read redirect_vendored_revert_failed from
the top-level warnings.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
main's #1278 e2e (sync_keeps_entry_whose_shared_cache_copy_is_still_patched)
reads the preview's prunableManifestEntries, which the one GC shape
renamed to prunedManifestEntries; the contract text it brought gets the
same rename.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Keep the envelope ports of e2e_cargo and the Cargo GC contract text
(pruned entries are details.manifest events, so the preview key rename
on #1273 does not apply here).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#1305's sync_keeps_entry_whose_shared_cache_copy_is_still_patched
asserted the scan --sync --dry-run preview via gc.prunableManifestEntries,
a key #1273 retires: under the one GC shape a dry run reports would-be
prunes as gc.prunedManifestEntries. The keep logic itself merged intact
(preview and wet pass both keep the still-patched itoa entry, and the wet
pass reports cargo_cache_patch_kept in gc.warnings[] with the purl and the
rollback remedy), so only the test read the wrong key. Assert the same
ryu-only list under the new key.

CLI_CONTRACT.md's scan --prune paragraph still named the retired preview
keys (prunableManifestEntries, revertableVendoredEntries,
vendorOrphanDirs); point it at the unified keys and note that the
preview, like every warnings[] entry, carries no cargo_cache_patch_kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	crates/socket-patch-cli/CLI_CONTRACT.md
Keep the envelope wording for the Cargo shared-cache GC keep (no event
and no warning in the preview) and take #1273's vendor_artifact_gitignored
NuGet/JVM contract updates with the envelope's top-level warnings[].
e2e_cargo keeps the events-based prune read with #1273's message.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Base automatically changed from fix/gc-report-json-1257 to main October 10, 2026 16:42
#1273 landed as squash e3eaa0c, whose tree is fix/gc-report-json-1257
(ff9704a, already merged here) plus #1342. Conflicts take this branch's
side throughout (the squash adds nothing beyond ff9704a); #1342's
nuget_feed.rs merges cleanly and its CLI_CONTRACT.md nuget drift note is
ported.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ports main's new output onto the envelope:
- hosted scan: NuGet stale-global-package (#352) and Go consumer-sync
  (#343) warnings join the top-level warnings[] as RunWarnings.
- get / agent download: vendored-uuid drift check reads the manifest
  project's ledger (project_root, #745) with the envelope's recorded pairs.
- e2e_nuget_dotnet_build: keeps summary.applied for the hosted pin and
  adds main's stale-global-package assertions.
- CLI_CONTRACT.md: main's NuGet mapping set-aside (#462/#354), the
  stale-global-package row (envelope wording), and the --manifest-path
  root note (#745) merged into the envelope text.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merged via the queue into main with commit efddc0d Oct 10, 2026
53 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the v5/json-envelope-unify branch October 10, 2026 20:40
Mikola Lysenko (mikolalysenko) added a commit that referenced this pull request Oct 10, 2026
Resolve hosted.rs: keep #1369's Envelope-based error path and carry
#1360's deferred --json error envelope (now an Envelope) so hosted
failure telemetry still goes out before the error prints.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko) added a commit that referenced this pull request Oct 10, 2026
Picks up #1369 (squashed) and #1343. scan/hosted.rs and scan/mod.rs on
main are identical to the v5/json-envelope-unify tip already merged, so
both conflicts resolve to the existing resolution.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

3 participants