The plan
Every run produces a machine-readable plan before it writes anything: whatif stops there, run continues. The plan is the backbone, not a debug flag. The compare job, the dashboard, the merge request text and the estate report read it.
- A plan explains why nothing happens. Every held update carries its reason, when the hold lifts and which rule imposed it.
- Nothing downstream of the plan invents a change. Branches carry byte-range edits and tasks; apply consumes those, never a dependency.
- Format: JSON,
--report <path>,%sfor the project path,schemaVersion1
The fields
Top level
| Field | Meaning |
|---|---|
pinupVersion, generatedAt | which pinup planned, and when |
repo.path | the project |
limits | prHourlyLimit, prConcurrentLimit as resolved; the runner enforces them |
dashboard | whether the dashboard issue is kept, and its title |
deps, updates, branches, warnings, stats | see below |
deps
One entry per dependency per file: manager, file, depName, packageName, currentValue, currentDigest, lockedVersion, datasource, versioning, registryUrls, the values from the pre-lookup rule pass (rangeStrategy, minimumReleaseAge, pinDigests), the locus (byte offsets of value and digest) and, for custom managers, the index and the captures. A dependency that was not looked up says why in skipReason; one disabled by a rule names the rule in disabled.
updates
One entry per movement: dep, newValue (the bytes written), newVersion, newDigest, updateType (major, minor, patch, digest, pin, pinDigest, rollback, lockFileMaintenance, majorAvailable), declared and effective, analyzer and evidence, releaseTime with timeSource, securityFix – and:
blocks: every reason the update is held for –reason,origin(configorpackageRules[N]),until,note. Reasons:minimumReleaseAge,schedule,dependencyDashboardApproval,disabled,allowedVersions,internalChecksFilter,hourlyLimit,concurrentLimit,rollingMajor,pluginRequired,taskRefused,nothingToRefresh,publishFailed.suppressedBy: the first reason, for readers who need one word.notes,compareUrl: the release notes between the versions and the forge's compare page.
branches
One entry per branch: name (Renovate-compatible), title, groupName, updateKeys, edits (file, byte range, old and new bytes, manager), tasks (lock refreshes and post-upgrade commands with their scope), automerge, labels, schedule, existing (the open merge request) and suppressedBy with heldWith where the branch is held as a whole.
warnings
stage (config, extract, lookup, analyze, apply, publish, cache), file, msg. An unreachable datasource, a preset that resolves to nothing, a task whose output left its scope, a cache that is too young: warnings, never a failed run for the rest of the repository.
Reading a plan
# every held update with its reason
jq -r '.updates[] | select(.blocks) | "\(.dep.depName) \(.dep.currentValue) -> \(.newValue): \(.blocks[0].reason) (\(.blocks[0].origin.source))"' plan.json
# the branches that would be written now
jq -r '.branches[] | select(.suppressedBy == null) | .name' plan.jsonNext
Effective label
The second label on every update: the Helm chart analyzer, the safety rule, matchEffective and trustEffective.