Skip to content

Coming from GitHub Actions

neosource CI aims to be familiar, not identical. Most of a GitHub Actions workflow carries over unchanged. Some of it carries over with behaviour you need to know about. Some of it does not work at all yet.

This page is the inventory. It is deliberately blunt: a compatibility page that oversells is worse than no page, because you find out at 2am.

  1. Move the files. .github/workflows/ is not read, and no diagnostic is produced. Move your workflows to .neosource/workflows/ or nothing will ever run. Only the top level of that directory is read, and only on your default branch.
  2. Replace images with packages. image: on a job is a hard parse error; container: is silently ignored. Jobs declare tools instead — see The job environment.
# before
container: node:20
# after
runs-on: nix
packages: [nodejs_20]

These behave as you expect.

Feature Notes
Workflow file syntax Same YAML shape, .yml or .yaml.
name, jobs, steps Unchanged.
run: steps Unchanged.
env: at workflow, job and step level Same precedence: step > job > workflow.
needs: Enforced server-side; dependants are skipped transitively when a dependency fails.
Step-level if: Evaluated.
strategy.matrix Free-form axes, plus include, exclude, fail-fast, max-parallel.
Job outputs: Unchanged.
continue-on-error, timeout-minutes Both job and step level.
working-directory: Unchanged.
shell: bash, sh, python, pwsh/powershell.
services: Real container images, with env, ports and healthcheck.
${{ … }} expressions Contexts: env, steps, needs, matrix, github, runner, inputs, secrets, vars, job, strategy.
${{ secrets.NAME }} Secrets are managed per workspace via the API.
actions/checkout Native implementation. Version suffix ignored.
actions/upload-artifact, actions/download-artifact Native implementations.
actions/setup-* toolchains Native: node, python, go, bun, java, dotnet, ruby, deno, zig, php, haskell, plus erlef/setup-beam, oven-sh/setup-bun, dtolnay/rust-toolchain.
Third-party actions Fetched from github.com as owner/repo@ref. Composite, Docker and Node (node12/16/20) actions run.

These work, but not exactly the way they do on GitHub.

Feature Caveat
Workflow location .neosource/workflows/, top level only, indexed from the default branch only. Subdirectories are ignored.
on: pull_request Fires on opened and synchronize only. Other types:reopened, closed, labeled — never fire, even if you list them.
Branch and path globs Custom matcher, anchored at both ends: * does not cross /, ** does. release/* matches release/v1 but not release/v1/rc1.
paths / paths-ignore Degrade to matching everything when the changed-path set is unknown or exceeds 1000 paths — the run happens rather than being skipped.
runs-on: Scheduling label only. No value selects a different image or OS.
Unmodeled GitHub keys permissions:, defaults:, run-name:, environment:, secrets: inherit are accepted, warned about, and ignored. Your workflow still runs.
container: Warned and ignored — the job runs in the canonical environment regardless.
with: inputs Must be scalars. A list or map value is a parse error.
Third-party actions Fetching requires network egress to github.com at job time.
Parse diagnostics Persisted and returned by the workflows API, but the web UI does not render them. A warning is effectively invisible unless you query the API.
Platform Linux only.

The diagnostics caveat compounds the others: an ignored container:, a dropped trigger, or a misspelled key produces a warning nobody sees. After migrating, it is worth reading GET /api/repos/{owner}/{repo}/workflows once and checking the warnings array on each workflow.

These do not work. Plan around them.

Feature Status
.github/workflows/ Not read at all, and no warning is produced. Silent no-CI.
image: on a job Hard parse error, with a pointer to packages:.
concurrency: Ignored. Two quick pushes start two runs and neither cancels the other. There is no cancel-in-progress.
Job-level if: Parsed but never evaluated — a condition on a job does not gate it. Use step-level if:.
tags: / tags-ignore: Parsed and discarded. A workflow filtering only on tags fires on every push.
on: workflow_dispatch Parses, but no endpoint dispatches it. Use POST /api/workflow-runs for manual runs.
on: repository_dispatch Parses, but nothing dispatches or receives it.
on: change Not implemented. A hard parse error when it is a workflow’s only trigger; a warning otherwise.
Reusable workflows (jobs.<id>.uses:) Not supported. That job is skipped with a warning; its siblings still run.
permissions: Ignored — no per-job token scoping.
Deployment environment: Ignored — no environments, approvals or protection rules.
Self-hosted runners The API exists, but the agent is not distributed. See Runners.
Windows and macOS runners Linux only.
Other trigger events release, issues, workflow_run and the rest warn and drop that trigger.

Putting it together:

name: CI
on:
push:
branches: [main]
pull_request:
env:
CARGO_TERM_COLOR: always
jobs:
test:
runs-on: nix
packages: [rustc, cargo]
strategy:
matrix:
rust: [stable]
steps:
- uses: actions/checkout@v4
- run: cargo test
lint:
runs-on: nix
packages: [rustc, cargo, clippy]
needs: [test]
steps:
- uses: actions/checkout@v4
- run: cargo clippy -- -D warnings

Note what is absent: no container:, no permissions:, no concurrency:. Those would parse, but three of them would be silently ignored.