This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
OpenFn kit is the pnpm monorepo behind OpenFn's workflow automation: the CLI, the Runtime that executes jobs, and the Worker that runs jobs in production for Lightning (OpenFn's web platform).
- @openfn/runtime executes a workflow plan in a sandboxed Node VM. A workflow is a graph of steps (jobs) joined by conditional edges. A job is an array of operations, each a function from state to state. State is never mutated between jobs.
- @openfn/compiler turns job DSL into a standard ES module that imports from its adaptor (an npm package such as
@openfn/language-http) and exports the operations array. - @openfn/cli is the local frontend. It compiles jobs itself and calls the runtime in-process.
- @openfn/ws-worker is the production frontend. It is stateless: it claims runs from Lightning over websockets and reports events back, and Lightning owns all persistence. It never imports the compiler. It hands plans to @openfn/engine-multi, which compiles and runs each one in a worker thread inside a child process.
- @openfn/project models local projects and workspaces (
Workspace,Project, YAML parsing, diffs, version hashes). The CLI'sprojecthandlers inpackages/cli/src/projects/are thin layers over it, and this is the most actively developed area of the repo. Sync with Lightning uses its v2 endpoint. - Supporting:
lexicon(shared types),logger,lightning-mock(fake Lightning for worker tests),deployanddescribe-package(both being phased out).
Every internal dependency is workspace:* and resolves to the dependency's built dist/, not its src/. engine-multi also runs its worker processes from dist/.
Node is pinned in .tool-versions (use asdf), and pnpm switches itself to the version pinned in packageManager (install any pnpm with npm install -g pnpm; corepack no longer ships with Node).
pnpm install
pnpm build # all packages
pnpm --filter @openfn/cli build # one package
pnpm test # all package tests
pnpm test:types # tsc per package
pnpm format / pnpm test:format # prettier write / check (there is no eslint)
pnpm _typesync # after changing dependenciesTests use ava with the shared root ava.config.js, which runs test/**/*test.ts straight from src/ via @swc-node/register. Because workspace deps come from dist/, run pnpm build first when a test crosses packages or uses engine-multi. When unsure, build.
cd packages/project
pnpm ava test/util/config.test.ts # one file
pnpm ava test/util/config.test.ts -m "name*" # tests matching a titlews-worker and engine-multi run with --serial; keep new tests there compatible with that.
- CLI:
cd packages/cli && pnpm openfn <command>runs the builtdist/, so rebuild (orpnpm build:watch) after changes.pnpm install:globalfrom root installs a globalopenfnxfor testing; remove it withnpm uninstall -g @openfn/clix. - Worker:
cd packages/ws-worker && pnpm startconnects to Lightning atlocalhost:4000.-l mockuses the mock Lightning,--mockuses a mock engine, and--no-loopstops auto-claiming so you can trigger claims withcurl -X POST http://localhost:2222/claim. Options and theirWORKER_*env vars are inpackages/ws-worker/src/util/cli.ts. - CLI env vars (
OPENFN_API_KEY,OPENFN_ENDPOINT,OPENFN_REPO_DIR,OPENFN_ADAPTORS_REPO, and others) are defined inpackages/cli/src/options.tsandpackages/cli/src/projects/options.ts.
These live in integration-tests/ (cli, execute, worker, legacy-deploy) and run against built packages. Always pnpm build first.
pnpm test:integrationfrom root runs them all.- In
integration-tests/cli,pnpm test:devuses the localpackages/cli/dist. Plainpnpm testexpects a globally installedopenfn, which is how CI runs it:pnpm pack:localthen install the tarball globally.
Commands are yargs modules registered in packages/cli/src/cli.ts. Each module calls ensure('<command-name>', ...) (util/command-builders.ts) to set argv.command, and packages/cli/src/commands.ts dispatches that string to a handler. A new project subcommand needs all three of:
- a yargs module added in
packages/cli/src/projects/command.ts; - the handler exported from
packages/cli/src/projects/index.ts; - an entry in the
handlersmap and theCommandListunion incommands.ts.
packages/cli/src/projects/list.ts is the simplest example to copy.
Every PR that changes a published package needs a changeset (pnpm changeset). The release note is a single, short, high-level sentence, and vague is fine (e.g. added new "compile-test" command to the CLI). Leave out files, functions and implementation detail. Breaking changes may add a short migration guide after the sentence. The package CHANGELOG.md files show the house style.
Releases are prepared by hand on a release branch: run pnpm changeset version, check the changelogs, run pnpm install, and commit. Merging to main then publishes to npm and builds the worker Docker image. A brand-new package must be published once first with pnpm publish:new; see README.md.
Read these before working in the matching area:
- .claude/event-processor.md: how the worker orders and batches events sent to Lightning.
- .claude/yaml-formats.md: v1 vs v2 project YAML, how each is detected, and how they convert.
- .claude/unit-testing-jobs.md: compiling a job so it can be unit tested.
packages/runtime,packages/engine-multiandpackages/ws-workereach have their ownCLAUDE.md, loaded automatically when you work there.