What changes between published versions that you have to act on. Each section names the versions it moves between, says what breaks, and gives the steps. Read every section between the version you run and the one you install.
On 0.6.0, a [needs-llm] handoff from a plugin under review_gate: true, or
declaring autonomy_level: 'supervised', parked in pending_gates and stopped
the advance after its level, whether or not the plugin declared a side effect.
On 0.7.0, a declared handoff from a plugin with side_effects: [] is never
parked. Its run log entry reads completed, plugin_runs reads skipped, and
the advance goes on. A dependent reads its Output in the same advance, before
anyone has reviewed it. gated, pending_gates and --strict no longer count
it.
If you relied on that park to review an effect-free handoff before its dependents ran, put the review on the dependent instead. A dependent that declares a side effect still needs its own approval, and its result is still parked under the review gate. A handoff from a plugin that declares a side effect parks as before.
needs-llm scanner reads Handoff records (breaking)Each declared handoff an advance or warpline run makes is now written to
<home>/handoffs/<run_id>.<plugin>.json. The needs-llm skill in the
warpline Claude Code plugin globs that directory and keeps the records whose
state is open. It no longer reads <home>/runs/.
warpline run still writes its delegated run artifact as before.
<home>/handoffs/ must be writable. If the record can’t be written, the
plugin’s result is failed with summary <plugin>: handoff not recorded, its
dependents skip as dependency_failed, and warpline run reports ok: false.
A caller of invokePlugin from warpline/unstable-runtime that passes its own
runsDir gets a handoffs/ directory beside that directory.
--strict counts only gates that can still be appliedA gate past its plugin’s ttl_hours, one a later run superseded, one whose
dependency re-ran, and one missing either run clock are no longer counted in
pending_gates, so --strict no longer exits 1 for them. approve already
refused each of them on 0.6.0.
notifyNothing is sent until you set notify in preferences.json. Once you do, a
bare approve <plugin>, approve <plugin> --content, deny <plugin> or
renew <grant-id> exits 1 with nothing written while an Ask it could answer
is open. Scripts that call those forms must answer with --nonce, --digest
and --principal, or run before notify is set.
0.6.0 refuses a preferences.json that carries notify. Run
warpline prefs set notify null before you downgrade. asks.json and
handoffs/ are left in place, and 0.6.0 doesn’t read them.
A plugin directory name must match ^[a-z][a-z0-9-]{0,63}$: lowercase letters,
digits and hyphens, a leading letter, at most 64 characters. Its manifest
name must equal the directory name, and each dependencies entry must follow
the same rule. The rule is published as PLUGIN_NAME and isPluginName from
warpline/schemas/plugin-name.
On 0.5.1 all of these loaded, and on 0.6.0 none does:
Mailer or
issue_render;name differs from its directory.Such a plugin is a load failure. It does not run, warpline plan names it, and
warpline run refuses it: a name outside the rule exits 1, and a manifest
whose name differs fails to load. PluginManifestSchema from
warpline/schemas/plugin-manifest rejects those manifests too, and
invokePlugin from warpline/unstable-runtime refuses the same names and
manifests the loader does, before it imports anything for a bad name.
To find them before you upgrade, run warpline plan on 0.6.0 against a copy
of your home, or check every directory under your plugin root against the
pattern above.
Rename while no advance is running. For a plugin moving from old to new:
<plugins>/old to <plugins>/new.name: 'new' in its manifest.ts.<home>/config/old.json to
<home>/config/new.json.old to new in the dependencies of every plugin that names it.old no longer covers new, and
neither does a standing grant. Both fail closed. Issue a new standing grant
for new with warpline approve new --standing …, and revoke the old one
with warpline revoke --standing <grant-id>.denials entry is keyed by the old
name and is lost by the rename: run warpline deny new again.State keyed by the old name stays behind and nothing reads it. plugin_runs
no longer covers the plugin, so it runs once more on the next advance that
admits it. History is not rewritten: the audit store and the run logs keep the
old name, and they stay readable, because what they carry follows a looser rule
than what the loader admits.