Repository navigation
Artifact GC is reported in four JSON shapes, and the bytesFreed/bytes fields the contract documents are emitted by no command #1257
Description
[agent] Filed by the scheduled architecture audit routine (CLI and core). Register: register comment.
Kind: bug. Source: new finding, register C79. It relates to C33 (#948, contract freshness) and C50 (#893, GC retention).
Problem (main @ a80b89e)
The contract's unified envelope documents three byte counters:
summary.bytesDownloadedandsummary.bytesFreed(CLI_CONTRACT.md#L1189-L1200);events[].bytes, which it says is "set" ondownloadedand optional onremoved(#L1221,#L1237-L1245).
None of them exists in the code. json_envelope::Summary has no byte fields, and PatchEvent has no bytes. The contract's own "GC summary" recipe, repair --json | jq '{removed: .summary.removed, bytesFreed: .summary.bytesFreed, …}' (#L1711-L1718), therefore returns bytesFreed: null. It also returns removed: 1 however many blobs were deleted.
The same GC result is reported four ways, with four key spellings:
| Command | JSON | Bytes freed in JSON |
|---|---|---|
repair |
one removed event, details: {count, checked} (repair.rs#L769-L780) |
no. It is computed, then sent only to telemetry (#L212-L217) and the human line |
remove |
removed event, details: {blobsRemoved, rolledBack, archivesRemoved} (remove.rs#L1061-L1068) |
no. It is printed in human mode only (#L936-L948) |
rollback |
legacy gc: {removedBlobs, removedDiffArchives, removedPackageArchives, bytesFreed} (rollback.rs#L1483-L1488) |
yes |
scan --prune |
gc: {prunedManifestEntries, removedBlobs, …, bytesFreed} (scan/gc.rs#L96-L106) |
yes |
The PatchAction table's "Emitted by" column has drifted too:
discoveredlistsscan, but onlylistemits it.downloadedlistsgetandscan --mode agent, but onlyrepairandupdateemit it.removedlistsrollback, but rollback prints its legacy shape and no events.verifiedlistsscan --dry-run, but scan prints no events.
Proof by execution (debug build at a80b89e, run twice under env -i, same result both times). The fixture is an agent-mode npm project: one applied patch, its before/after blobs, and four unreferenced blob files in .socket/blobs/. repair --offline --json deleted 5 of the 6 blobs. The contract recipe printed {"removed": 1, "bytesFreed": null, "failed": 0}, and the only event was {"action":"removed","details":{"count":5,"checked":6}}. The human run of the same fixture prints Removed 3 unused blobs (38 B freed). remove pkg:npm/left-pad@1.3.0 --json emitted two removed events (the entry, and blobsRemoved: 3), with summary.removed: 1 and no byte count.
Symptoms
None filed. #1066 (rollback counters count only the agent leg) is the same class of counter drift.
Impact
A CI or dashboard that follows the documented recipe gets null and a removal count of 1. The four shapes mean any consumer has to special-case each command. The risk is low, but the documented contract is wrong today, and every new GC caller adds another shape.
Proposed change
- Add one
GcReport { removed_blobs, removed_diff_archives, removed_package_archives, bytes_freed }injson_envelope(or besideArtifactReferences), serialized identically everywhere.rollbackandscanbuild it instead of their hand-writtenjson!blocks; the keys are unchanged. repairandremoveattach it to their artifact-levelremovedevent as additivedetailskeys, keepingcount/checkedandblobsRemoved/archivesRemovedfor compatibility.- Either implement
summary.bytesFreed(the sum of the removed events'bytes), or deletesummary.bytesDownloaded,summary.bytesFreedandevents[].bytesfrom the contract. Fix the jq recipe and the "Emitted by" column to match. Recommendation: implementbytesFreed, which is additive and MINOR; deletebytesDownloaded/downloaded.bytes, because blob downloads don't carry a byte count today and Remove --download-mode and the diff download path (#792) #1049 rewrites that path. - Delete the four hand-written GC JSON blocks.
Size and scope
json_envelope.rs,repair.rs,remove.rs,rollback.rs,scan/gc.rsandCLI_CONTRACT.md.- About 80–150 production lines.
- Out of scope: what GC deletes (repair and scan --prune delete the beforeHash blobs of active patches, so a later offline rollback fails and tells the user to run repair #893) and rollback's top-level counters (rollback --json counters count only the agent leg, so hosted and vendored failures report failed: 0 #1066).
Acceptance criteria
-
repair --jsonandremove --jsonreport blobs removed and bytes freed under the same keys asrollback/scan --prune. - The contract's GC recipe returns a number for
bytesFreed(or the recipe is rewritten to the emitted keys), and an e2e test runs the recipe's paths. - Every
summaryandPatchEventfield documented in the contract is serialized byjson_envelope. A unit test pins the key set against the contract's schema block. - The "Emitted by" column matches the emitters.
- Existing
rollback_duality_invariantsandcovgap_commands_scan_vendor_flowGC assertions stay green.
Dependencies
- Blocks nothing.
- Coordinate with Remove --download-mode and the diff download path (#792) #1049, which changes repair's
downloadedevent, and Tracking: make CLI_CONTRACT.md a checked reference plus short prose #948 (contract freshness tests).
Activity
audit-core: C79 (#1257), Part 7 route passage after #1041
mikolalysenko commentedon Oct 9, 2026
[agent] Triaged p3 (general CLI JSON contract, no single ecosystem). Not a duplicate: #1066 is the same class of counter drift but a different fix (rollback's top-level counters only count the agent leg), and #948 is the broader contract-freshness tracking issue. No open PR addresses the GC report shape yet. Left open as an actionable bug.
Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
v5 release blocker (P1). Align the emitted GC JSON and documented counters before v5 consumers rely on them. A truthful small schema is sufficient; PR #1273 is pending.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
Report artifact GC in one JSON shape and count every rollback leg (#1257