Election chooses a coordinator. A lock chooses a task owner.

Choose between leader election and a named task lock
Your questionUseTypical work
Who coordinates this group right now?/electionsSchedulers, controllers, reconcilers, dispatchers
Who owns this exact item right now?/locks/:nameJobs, issues, deploys, migrations, customer tasks

Claim before you change anything.

Use a stable name that every worker can derive from the work itself. One caller receives acquired, a lease ID, and a fencing token. Others receive held and leave the task alone.

shell · claim invoice 1842
curl -s -X POST \
  https://your-octostore.internal/locks/invoice-1842/acquire \
  -H "Authorization: Bearer $OCTOSTORE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "ttl_seconds":120,
    "metadata":"worker=billing-7 trace=2f91"
  }'

The lease is the recovery plan.

AcquireGet one bounded claim before work starts.
RenewHeartbeat while the owner remains healthy.
ReleaseReturn the task immediately after completion.
ExpireRecover automatically when the owner vanishes.

A lock is advisory, not magic. Keep downstream operations idempotent and carry the fencing token into systems that can reject a stale owner.

Coordinate a process, not your whole product.

Sessions tie many ephemeral locks to one worker heartbeat and release them together when that worker disappears.

Metadata makes ownership understandable with a runner ID, trace, task URL, branch, or runbook hint.

Watches and webhooks let operators and reconcilers react to acquisition, renewal, release, and expiry.

Hierarchical names keep ownership legible across repos, tenants, queues, and work types.

One binary. One database file. Your boundary.

OctoStore runs as a Rust binary backed by SQLite in WAL mode. Start with static bearer tokens. Add GitHub OAuth only if humans need it. Put TLS and network policy in the reverse proxy you already operate.

shell · install and run
curl -fsSL \
  https://raw.githubusercontent.com/octostore/octostore.io/main/install.sh \
  | sh

STATIC_TOKENS='ops:change-me' \
DATABASE_URL='./octostore.db' \
octostore