2.7 Lab: author a pack action and run it off the tree
Included from Basic tier
After this you can: apply lessons 2.1–2.6 without new verbs: author a pack action of your own, plan it, run it off the working tree, and rehearse the commit.
Before you start — tick each one:
- Lesson 2.5 — Building a pack (
opschain learn 2.5) - Lesson 2.6 — The local development loop (
opschain learn 2.6)
In the GUI: none — the lab is a working-tree exercise.
This lab introduces nothing new. You will author one pack action with an
input and a summary, plan it, run it off your working tree, rehearse the
commit, and push — every step with the output it should show. Work in
the repository lesson 2.1 scaffolded (any git repository with at least
one commit and an .opschain/ directory will do). Each step ends with
the lesson to re-read if your output did not match.
1. Author the pack action
Create .opschain/actions/mypack/banner.yaml:
action: banner
summary: Print a banner naming the release
inputs:
release: { required: true }
steps:
- name: print
run: echo "=== release ${{ inputs.release }} ==="
The directory mypack/ makes the action mypack:banner; the inputs:
block declares the one value a caller must supply. Check the catalog
picked it up:
opschain actions list --local
ACTION SOURCE INPUTS APPROVALS STATUS SUMMARY
hello repo no no Example action scaffolded by opschain init
mypack:banner repo no no Print a banner naming the release
If a step did not show that, re-read lesson 2.5 (the namespace
directory, summary:) or lesson 2.1 (actions list, a file listed as
invalid: with the reason).
2. Plan it
opschain runs create change mypack:banner --env dev --plan --local -i release=1.2.0
· mypack:banner
└─ · print
Then plan it again without the input, to see the refusal the plan owes you:
opschain runs create change mypack:banner --env dev --plan --local
Error: action mypack:banner input release is required and was not supplied
If a step did not show that, re-read lesson 2.2 (inputs and how they are validated at compile time).
3. Run it off the working tree
Nothing is committed yet; a local run snapshots the working copy anyway:
opschain runs create change mypack:banner --env dev --local -i release=1.2.0
2026-11-07T00:25:50Z mypack:banner pending -> queued
2026-11-07T00:25:50Z mypack:banner queued -> running
2026-11-07T00:25:50Z mypack:banner/print pending -> queued
2026-11-07T00:25:50Z mypack:banner/print queued -> running
[mypack:banner/print] === release 1.2.0 ===
2026-11-07T00:25:50Z mypack:banner/print running -> success
2026-11-07T00:25:50Z mypack:banner running -> success
run 01a1b365-b942-7bb1-8a7c-6761728c5d89 success (0.1s)
The exit code is 0. Read the log back by run id:
opschain logs 01a1b365-b942-7bb1-8a7c-6761728c5d89 --local
[mypack:banner/print] === release 1.2.0 ===
If a step did not show that, re-read lesson 2.1 (the local run and its
exit codes) or lesson 2.6 (runs list|show and logs with --local).
4. Rehearse the commit with --ref
First prove the rehearsal catches an uncommitted file — run the ref before committing:
opschain runs create change mypack:banner --env dev --local --ref main -i release=1.2.0
Error: action "mypack:banner" has no declarative action file, so typed inputs cannot be supplied — inputs bind to an action file's inputs: declarations (see: opschain docs orchestration-logic)
The commit on main does not carry the file, so the ref run cannot find
the action — exactly the gap the rehearsal exists to expose. Now commit
and run the ref again:
git add .opschain/actions/mypack/banner.yaml
git commit -m "mypack: banner action"
opschain runs create change mypack:banner --env dev --local --ref main -i release=1.2.0
2026-11-07T00:25:51Z mypack:banner/print queued -> running
[mypack:banner/print] === release 1.2.0 ===
2026-11-07T00:25:51Z mypack:banner running -> success
run 01a1b365-bc34-731b-992e-469b83b00afa success (0.1s)
opschain runs show 01a1b365-bc34-731b-992e-469b83b00afa --local
Run: 01a1b365-bc34-731b-992e-469b83b00afa
Kind: change
Action: mypack:banner
Target: myrepo/dev
Status: success
Commit: eaa9ecfe14a700eb12dd773fb5e540e1281a90f5
...
✔ mypack:banner — success (0.1s)
└─ ✔ print — success (0.0s)
The Commit: line is the revision you just committed — the run executed
the commit, not your tree.
If a step did not show that, re-read lesson 2.6 (rehearsing with
--ref).
5. Push and promote
Push the commit:
git push origin main
What you pushed is now all a server can see of your work. Promotion happens on the server lane, in the next module: a change on an environment of yours runs this action with the server keeping the record (lesson 3.1), and a template version pins the exact commit you just rehearsed so pinned assets run precisely that (lesson 3.4). When you get there, the create line you are looking for is:
created change 01a1… (mypack:banner on acme/dev) — pending
If a step did not show that, re-read lesson 2.6 (why a server never
reads your local disk) — and then opschain learn 3.1.
Check yourself
- Does your pack action plan clean, refuse without its input, run green off the tree, and run green again with
--refpointing at the committed revision — withruns shownaming that commit?
What you learned
- A pack action is a namespaced action file with a
summary:and declaredinputs:, listed byactions list --local. --plancatches a missing input before anything runs;--localruns your working copy;--refruns only what is committed.- A ref run against a commit that lacks the file is refused by name — the rehearsal exists to catch exactly that.
- Pushing the rehearsed commit is the hand-off to the server lane, where lessons 3.1 and 3.4 promote it.
Next: lesson 2.8 — Running OpsChain in CI (opschain learn 2.8), or back to your track (opschain learn).