How CI works here
neosource runs CI from workflow files stored in your repository. The shape will be familiar if you have used GitHub Actions, but two things differ enough to cover before you write anything: where the files live, and which triggers actually fire.
Where workflows live
Section titled “Where workflows live”.neosource/workflows/├── ci.yml└── nightly.yamlThree rules, and each of them has bitten someone:
- The directory is
.neosource/workflows/, not.github/workflows/. - Files must end in
.ymlor.yaml. - Only the top level of that directory is read. Files in subdirectories are not picked up.
.github/workflows/ is not read
Section titled “.github/workflows/ is not read”If your repository has a .github/workflows/ directory, neosource ignores it
completely. It is not read, not parsed, and no diagnostic is produced — a
repository imported from GitHub with a full set of Actions workflows will simply
have no CI, silently, until you move the files.
This is the single most likely reason for “why is nothing running”. See Coming from GitHub Actions for what to do with the files once you move them.
How workflows get registered
Section titled “How workflows get registered”Workflows are indexed from the default branch only. When you push to it,
neosource reads .neosource/workflows/ out of the tree that push just created
and replaces the repository’s registered workflow set with what it finds.
Two consequences follow from “replaces”:
- A workflow file added in a push is registered before that same push’s triggers are evaluated — so a workflow you add can fire on the very push that adds it.
- A workflow file deleted from the tree is deregistered. There is no stale leftover definition.
Pushes to other branches do not change the registered set. A workflow that only exists on a feature branch is not registered, though it can still run against that branch once it is on the default branch — the definition comes from the default branch, the code under test does not.
What actually triggers a run
Section titled “What actually triggers a run”The parser accepts more trigger events than the server dispatches. Three events start a run today:
| Trigger | Fires when |
|---|---|
push |
You push to the repository. |
pull_request |
A PR is opened, or updated with new commits (synchronize). |
schedule |
A cron entry comes due. |
workflow_dispatch, repository_dispatch and change parse but do not
dispatch. The full status table and the filter rules are in
Writing a workflow — including the glob
semantics, which are stricter than most people expect (release/* does not
match release/v1/rc1).
To start a run by hand, call POST /api/workflow-runs with a workflow_id and
a cause. It needs Write permission on the repository.
There is no concurrency: support
Section titled “There is no concurrency: support”A concurrency: block is ignored. Pushing twice in quick succession starts
two runs and neither cancels the other. If you rely on GitHub’s
cancel-in-progress behaviour to keep your queue clean, that behaviour is not
here yet; cancel the superseded run yourself via
POST /api/workflow-runs/{run_id}/cancel.
Job ordering
Section titled “Job ordering”Within a run, needs: is enforced server-side. A job waits until everything it
depends on has finished, and if a dependency fails, the jobs downstream of it are
skipped transitively rather than run against a broken state.
Step-level if: conditions are evaluated. Job-level if: is not — a
condition written on a job does not gate that job. Put the condition on the
steps instead.
Seeing diagnostics
Section titled “Seeing diagnostics”When a workflow file is indexed, its parse diagnostics are stored alongside it:
unknown keys and unsupported triggers as warning, a whole-file parse failure as
error. A file that fails to parse is still indexed — as an empty definition
that can never run — so it shows up as broken rather than vanishing from the
list.
Read them from the workflows API:
GET /api/repos/{owner}/{repo}/workflowsEach workflow in the response carries a warnings array with a severity and a
message.
The web UI does not currently render these diagnostics. The Actions page
lists workflows by path and name only. So a workflow that parsed with warnings —
an ignored container:, a dropped trigger, a misspelled key — looks completely
normal in the UI. Until that gap closes, the API is the only place to see them,
and it is worth checking after any migration.
Watching a run
Section titled “Watching a run”| Purpose | Endpoint |
|---|---|
| List a repo’s runs | GET /api/repos/{owner}/{repo}/workflow-runs |
| Runs for one commit | GET /api/repos/{owner}/{repo}/commits/{sha}/workflow-runs |
| One run | GET /api/workflow-runs/{run_id} |
| Jobs in a run | GET /api/workflow-runs/{run_id}/jobs |
| Job logs | GET /api/jobs/{job_id}/logs |
| Follow logs live | GET /api/jobs/{job_id}/logs/stream |
| Test/lint annotations | GET /api/runs/{run_id}/annotations |
| Cancel a run | POST /api/workflow-runs/{run_id}/cancel |
| Re-run | POST /api/workflow-runs/{run_id}/rerun |
| Cancel one job | POST /api/jobs/{job_id}/cancel |
Full schemas are in the API reference. There is also an Actions view in the web UI covering the run list, the job graph, steps and logs.
- Writing a workflow — the schema in detail.
- The job environment — why jobs declare
packages:instead of an image. - Coming from GitHub Actions — the honest compatibility table.