/onex:ship_dirty_canonical — Auto-ship dirty canonical repos
Skill ID: onex:ship_dirty_canonical
Version: 1.0.0
Owner: omniclaude
Backing node: omnimarket/src/omnimarket/nodes/node_dirty_canonical_sweep/
Problem it solves
Agents can edit files in canonical workspace repo clones but cannot commit there. Those edits sit uncommitted, are invisible to CI, and are at risk during routine sync workflows.
This skill detects dirty canonicals and rescues the edits by:
- Running status detection on every configured repo
- Creating a worktree from
devat$ONEX_WORKTREES_ROOT/auto-ship-<ts>/<repo> - Copying the dirty files there and committing
- Pushing and opening a PR with
--label auto-ship - Restoring the canonical clone to clean state only after publishing succeeds
Usage
/ship-dirty-canonical
/ship-dirty-canonical --dry-run
/ship-dirty-canonical --repos omniclaude,omnimarket
/ship-dirty-canonical --dry-run --repos omnibase_core
Dispatch
The backing handler is fully synchronous in-process git/gh subprocess work
(detect → worktree → copy → commit → push → PR → restore). It runs locally via
the packaged module entrypoint — pass options as argparse flags, not a JSON
payload:
# Run from the omnimarket project so the node package is importable.
OMNIMARKET_PROJECT="${OMNI_HOME:?set OMNI_HOME}/omnimarket" # local-path-ok: operator-workspace surface; the workspace root deliberately keeps its OMNI_HOME spelling here
uv run --project "$OMNIMARKET_PROJECT" \
python -m omnimarket.nodes.node_dirty_canonical_sweep \
[--dry-run] [--repos REPO ...] [--omni-home PATH] \
[--worktrees-root PATH] [--pr-label LABEL] [--base-branch BRANCH]
Do not dispatch this node via
onex run-node(the Kafka path). That command routes over the bus and waits for a terminal event, but this node has no live consumer, so it always fails withSkillRoutingError "Timeout after 30s waiting for response". The localonex node/onex runalias is pinned to--project omnibase_infra, whose installed metadata does not register this node'sonex.nodesentry point, so it raisesUnknown nodeas well. The in-process module entrypoint above is the only path that resolves and runs.
The entrypoint prints ModelDirtyCanonicalSweepResult as JSON on stdout:
repos_checked: total repos inspectedrepos_dirty: repos that had uncommitted changesrepos_shipped: repos successfully shipped to PRsrepos_failed: repos where ship failedresults: per-repoModelDirtyRepoShipResultentries (only dirty repos)
A non-zero exit means the handler raised; surface its error output directly and do not produce prose.
Safety invariants (enforced by node handler, not this skill)
never_commits_to_canonical_repo— the canonical clone is restored by the backing node only after successful shippingrestores_canonical_to_clean_state_on_success— canonical changes remain in place if publishing the rescue branch failsone_worktree_per_dirty_repo— creates at most one worktree per dirty repo per invocation
Build-loop integration
This skill is designed to be invoked by the 15-minute build-loop cron. Wire it as a CronCreate job to run every 15 minutes:
CronCreate(interval="15m", prompt="/ship-dirty-canonical")
The build loop can also invoke it explicitly during the VERIFYING phase as a pre-condition before dispatching new tickets.
Backing node contract
omnimarket/src/omnimarket/nodes/node_dirty_canonical_sweep/contract.yaml