Skip to main content
Version: v5.6.1

2.6 The local development loop

Included from Basic tier

After this you can: iterate on actions, packs and template content on your own machine — plan, run off the working tree, rehearse the commit with --ref — and promote once it works.

Before you start — tick each one:

  • Lesson 2.1 — Your first action (opschain learn 2.1)

In the GUI: none until you push and promote; then the asset's page shows the template pin that carries your commit.

Actions, packs and template content are repository files — and you do not have to push to git to try an edit. The loop is: author in your working tree, plan it with --plan --local, run it with --local straight off your uncommitted working copy, rehearse the finished commit with --ref, and only then push and promote — a change on a server (lesson 3.1) or a template version pinning the commit (lesson 3.4). Edit, run, edit, run — commit when it works, not before. This lesson names that loop end to end and shows you where the local lane keeps its history.

1. Iterate on the working tree​

A local run executes a snapshot of your working copy — tracked changes and untracked-but-not-ignored files included, so you can iterate without committing; git-ignored files are never part of a run:

opschain runs create change deploy --env dev --local

Edit the action file, run again. The run executes right in your terminal — live step tree, streamed logs — and the process exit code is the run's outcome. No server, no runner, no push.

2. Plan before you run​

--plan resolves the step tree without executing anything:

opschain runs create change deploy --env dev --plan --local
· deploy
├─ · build
└─ · release

Use it after every structural edit — composition mistakes, unknown inputs and expression errors surface here, before anything runs. A plan is always advisory: the run re-evaluates every condition at step entry.

3. Local history and logs​

Your run history is local too, with the same verbs a server run has:

opschain runs list --local
ID KIND ACTION TARGET STATUS CREATED DURATION
01a1b365-bc34-731b-992e-469b83b00afa change mypack:banner myrepo/dev success 2026-11-07T00:25:51Z 0.1s
01a1b365-b417-754b-9f25-fdca2a6b7f39 change release myrepo/dev success 2026-11-07T00:25:49Z 0.2s
01a1b365-b05e-7026-9ab3-edd93e92d8b2 change hello myrepo/dev success 2026-11-07T00:25:48Z 0.2s
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
Created: 2026-11-07T00:25:51Z
Started: 2026-11-07T00:25:51Z
Finished: 2026-11-07T00:25:51Z
Duration: 0.1s

✔ mypack:banner — success (0.1s)
└─ ✔ print — success (0.0s)
opschain logs 01a1b365-bc34-731b-992e-469b83b00afa --local
[mypack:banner/print] === release 1.2.0 ===

Where it lives: a per-user SQLite store under your opschain home, <opschain home>/local/opschain.db — created on first use, nothing to start and nothing to configure, and apart from the store a server on the same home would use (<opschain home>/opschain.db), so running a server and local experiments from one home never mixes the two. opschain state doctor prints the file and its keyset (with a server context active it, too, needs --local — it is the local store's doctor, not the server's):

opschain state doctor --local
OWNER VERSION STATUS
store: /home/alice/.opschain/local/opschain.db
keyset: /home/alice/.opschain/local/keys.env (present, mode 0600)

opschain state reset --project <code> clears a project's local history when you want a clean slate (opschain docs cli/state). A local store from an earlier release that still sits at <opschain home>/opschain.db is moved into place by the first local command that opens it — one line on stderr names both paths — unless a server is configured in that home, in which case that file is the server's and local mode starts fresh (opschain docs storage, "Local mode").

Your local keyset. The first local command that opens the store also generates <opschain home>/local/keys.env (mode 0600): sensitive step outputs (opschain step set-output --sensitive …) and the database property and settings layers are encrypted with it at rest, exactly as a server encrypts them with its own keys — nothing sensitive sits in the file in plaintext. There is nothing to configure, but two things to know: keep local/keys.env with local/opschain.db (without it the store refuses to open, and a replaced keyset makes the old values unreadable), and a project reset keeps it — state reset --purge-keys removes it only once nothing in the store is still encrypted with it. opschain docs storage ("Local mode") has the full rules.

4. Secrets in local runs​

opschain secret set|get|ls|delete manages the local secret store: a per-user, per-machine store for the --local lane, separate from any server's vault:

opschain secret set db/password
secret db/password set
opschain secret ls
db/password

The value is read with echo disabled, never from the command line. Local runs resolve the same secret-vault://<path>/<key> references through that store, so an action authored against the vault works unchanged on your laptop — and nothing in the local store ever reaches a server. Lesson 3.5 covers both stores.

5. Developing packs locally​

The same loop grows a pack. Author the future pack's actions as a namespace directory in the repository you are working in (.opschain/actions/mypack/banner.yaml is mypack:banner) and iterate with --local; split it into its own repository when it is ready to share, declaring it with url: and a branch rev: while iterating — each re-resolution re-pins at the branch's head. If you distribute the pack as a signed bundle, bundle: <path> mounts the built .bundle file itself in a local run before you publish it. Lesson 2.5 walks the whole pack loop.

6. Rehearse a clean commit with --ref​

The working-copy snapshot is for iterating; before you push, prove the commit. --ref <git-ref> runs a clean pinned commit instead of your working copy:

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)

runs show on that run carries a Commit: line naming the exact revision it executed. If the run passes off the ref but failed off your tree (or the reverse), an uncommitted or git-ignored file is doing work the commit does not carry — exactly what this rehearsal exists to catch. An action file that exists only in your working tree is refused by name when you --ref a commit that does not carry it yet.

7. Promote: push once it works​

When the ref rehearsal is green, push — and the pushed commit is what every server lane consumes. Changes on a server run your action against real estate nodes with the server keeping the record (lesson 3.1). Template versions pin an exact commit of your repository, so the commit you rehearsed is precisely what pinned assets will run (lesson 3.4). One honest rule underneath it all: a server never reads your local disk — only what you push exists for it. opschain learn 3.1 is the next step on the server.

Check yourself​

  • What does --ref change about a local run, and what is the last step before a server can run what you wrote?

What you learned​

  • The loop is author → plan → run off the tree → rehearse with --ref → push and promote; commit when it works, not before.
  • runs list|show and logs with --local read your local history from <opschain home>/local/opschain.db, encrypted with its own keyset.
  • The local secret store resolves the same secret-vault:// references your actions use on a server, and never leaves your machine.
  • --ref runs a clean commit instead of your working copy, and runs show records which commit; a server only ever sees what you push.

Next: lesson 2.7 — Lab: author a pack action and run it off the tree (opschain learn 2.7), or back to your track (opschain learn).