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.
Start here: two things to do first
Section titled “Start here: two things to do first”- 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. - 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.
# beforecontainer: node:20
# afterruns-on: nixpackages: [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. |
Works, with caveats
Section titled “Works, with caveats”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.
Not yet
Section titled “Not yet”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. |
A migrated workflow
Section titled “A migrated workflow”Putting it together:
name: CIon: 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 warningsNote what is absent: no container:, no permissions:, no concurrency:. Those
would parse, but three of them would be silently ignored.
See also
Section titled “See also”- Writing a workflow — the full accepted schema.
- The job environment —
packages:in depth. - How CI works here — discovery, triggers and diagnostics.