GitHub Action that submits jobs to the EmbeddedCI server in two modes:
- YAML-only mode: send pipeline definition text.
- Archive mode: send source bundle +
embeddedci_yamlusing multipart upload.
Ensure your workflow checks out the repository first. This action authenticates with the
api_key input only, so the job needs no extra permissions: (unlike upload-artifact, which
needs id-token: write).
How the modes differ:
- Archive mode archives the contents of
source_pathand sends that archive to the server (withembeddedci_yamlfor which pipeline file to use inside the bundle). - YAML-only mode sends nothing except the pipeline YAML. Use it when source files are publicly accessible via Git (or similar) as specified inside the YAML, so the server can fetch them without an upload from this action.
jobs:
submit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: embeddedci-com/submit-job-action@v1
with:
api_key: ${{ secrets.EMBEDDEDCI_API_KEY }}
source_path: .jobs:
submit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: embeddedci-com/submit-job-action@v1
with:
api_key: ${{ secrets.EMBEDDEDCI_API_KEY }}
embeddedci_yaml: embeddedci.yaml| Input | Required | Default | Description |
|---|---|---|---|
api_key |
Yes | - | API key for EmbeddedCI. Use secrets.EMBEDDEDCI_API_KEY. |
api_url |
No | https://api.embeddedci.com |
EmbeddedCI server base URL. Override for self-hosted or staging. A value without a scheme gets https:// prepended. |
source_path |
No | empty | Path to what should be uploaded in archive mode. You can pass a directory (including .) and the action creates the archive automatically, or pass an existing archive file (.tar.gz, .tgz, .tar, .zip). |
embeddedci_yaml |
No | empty | Pipeline YAML path. In YAML-only mode this is the repo file path (defaults to embeddedci.yaml when omitted). In archive mode this optionally overrides auto-detection (embeddedci.yaml). |
ref |
No | branch name from GitHub context | Ref associated with the submission. Auto-detected from GITHUB_HEAD_REF (PRs) or GITHUB_REF_NAME. |
commit |
No | GITHUB_SHA |
Commit SHA associated with the submission. |
- uses: embeddedci-com/submit-job-action@v1
with:
api_key: ${{ secrets.EMBEDDEDCI_API_KEY }}
api_url: https://ci.mycompany.com
embeddedci_yaml: .embeddedci/job.yaml- uses: embeddedci-com/submit-job-action@v1
with:
api_key: ${{ secrets.EMBEDDEDCI_API_KEY }}
source_path: .- uses: embeddedci-com/submit-job-action@v1
with:
api_key: ${{ secrets.EMBEDDEDCI_API_KEY }}
source_path: firmware/- uses: embeddedci-com/submit-job-action@v1
with:
api_key: ${{ secrets.EMBEDDEDCI_API_KEY }}
source_path: firmware/
embeddedci_yaml: ci/embeddedci.yaml- uses: embeddedci-com/submit-job-action@v1
with:
api_key: ${{ secrets.EMBEDDEDCI_API_KEY }}
source_path: .
ref: ${{ github.ref_name }}
commit: ${{ github.sha }}- uses: embeddedci-com/submit-job-action@v1
with:
api_key: ${{ secrets.EMBEDDEDCI_API_KEY }}
source_path: repo.tar.gzOutputs are only set when the server includes the matching field in its response; an unset output reads as an empty string in later steps.
| Output | Description |
|---|---|
job_id |
Set when the server returns a job id in the response. |
job_status |
Set when the server returns a job status. |
job_builds |
JSON-encoded builds payload when present. |
source_metadata |
JSON-encoded source metadata when present. |
- Prefer passing a directory path (for example
.) and let the action archive it automatically. - Exclude large/unneeded files such as
.git, build outputs, and caches when creating archives manually. - Include
embeddedci.yamlin the archive root when using default auto-detection. - Set
embeddedci_yamlwhen your pipeline file lives at a custom archive path.
npm install
npm run buildThe built action is in dist/. Commit dist/ so the action works when used from GitHub.
| Action | Use it for |
|---|---|
embeddedci-com/submit-job-action@v1 |
Submit a pipeline or source archive to the EmbeddedCI build system. |
embeddedci-com/submit-job-action/upload-artifact@v1 |
Publish a firmware you built yourself, so it appears in the BenchPod flash dropdown. |
embeddedci-com/submit-job-action/emi@v1 |
Run EMI analysis on a KiCad or Gerber board and gate the build on the findings. |
Records a build you produced in your own workflow as a GitHub-sourced build on embeddedci.com and attaches the firmware to it. No BenchPod is touched, so it is safe to run on every push.
permissions:
id-token: write # required: the action authenticates with the job's OIDC token
contents: read
steps:
- uses: actions/checkout@v5
# ... your existing build ...
- uses: embeddedci-com/submit-job-action/upload-artifact@v1
with:
firmware: build/app.elf
build_target: stm32f4
openocd_target: target/stm32f4x.cfg
swclk: "11"
swdio: "12"
efuse: "1"The firmware's siblings (.elf / .bin / .hex / .uf2 with the same stem) are uploaded
alongside it, and the wiring inputs pre-fill the web UI's flash dialog. The repository must be
trusted in the EmbeddedCI web app under BenchPod → GitHub Actions; without that, or without
id-token: write, the step fails with a message naming what is missing.
Set allow_missing_token: "true" to downgrade that failure to a skip — useful for pull requests
from forks, which cannot mint an OIDC token.
| Input | Required | Default | Notes |
|---|---|---|---|
firmware |
Yes | - | Path to the built firmware (.elf, .bin or .hex). Siblings with the same stem (.elf, .bin, .hex, .uf2) are uploaded too. |
build_target |
No | empty | Platform id recorded against the build, for example stm32f4. Shown on the Builds page. |
openocd_target |
No | empty | OpenOCD target config for the DUT, for example target/stm32f4x.cfg. Pre-fills the flash dialog. |
swclk |
No | empty | LA channel (1-14) wired to SWCLK. Pre-fills the flash dialog. |
swdio |
No | empty | LA channel (1-14) wired to SWDIO. Pre-fills the flash dialog. |
nreset |
No | empty | true when the target's NRST is wired to the pod's reset pin (DUT header J1 pin 22). Leave empty when it is not wired. An LA channel number from older workflows is still accepted and read as "wired". |
efuse |
No | empty | Target-power eFuse rail: 1 = internal 5 V, 2 = external. |
name |
No | server-side commit summary | Build name shown in the web UI. |
api_base |
No | https://www.embeddedci.com |
EmbeddedCI base URL. When empty, the SDK reads BENCHPOD_API_BASE from the environment before falling back to the public server. |
sdk_ref |
No | empty | Tag, branch or commit of embeddedci-python to install instead of the PyPI release (see below). |
python_version |
No | 3.12 |
Python that actions/setup-python installs for the upload step. |
allow_missing_token |
No | false |
true succeeds with a skip instead of failing when no OIDC token is available (for example a fork PR). |
| Output | Description |
|---|---|
build_id |
The embeddedci build id the artifacts were attached to. Empty when the upload was skipped by allow_missing_token. |
The step installs the embeddedci SDK from PyPI (embeddedci==2.5.*). Set sdk_ref to a tag,
branch or commit of embeddedci-python to
install that instead, for example to try an unreleased fix.
Implementation note: this is a composite action wrapping the embeddedci-upload-build command from
the embeddedci Python SDK, which is the same
code path the pytest build_report fixture uses. Keeping one implementation avoids a second copy of
the OIDC exchange and build API drifting from the server.
Runs the EMI Analyzer's rules tier against a board committed to the repository, annotates the findings on the workflow run, and fails the build on the severity you choose. It takes seconds — this is the geometric tier, not a full-wave solve — so it is cheap enough for every push that touches the layout.
steps:
- uses: actions/checkout@v5
- uses: embeddedci-com/submit-job-action/emi@v1
with:
api_key: ${{ secrets.EMBEDDEDCI_API_KEY }}
board: hardware/mainboard.kicad_pcbOnly run it when the layout actually changed, or it will re-analyse an unchanged board on every commit:
on:
push:
paths:
- "hardware/**.kicad_pcb"Auth is an API key, not the OIDC token upload-artifact uses. EMI runs are filed under the
organisation that owns the key, so the credential has to identify a person rather than a
repository. Generate one in the web app under Settings → API keys with the emi:analyze
scope, and store it as a repository secret.
| Input | Default | Notes |
|---|---|---|
api_key |
required | Needs the emi:analyze scope. |
board |
required | A .kicad_pcb, a zip of the KiCad project, or a zip of Gerbers + drill + IPC-D-356 netlist. |
project |
repository name | Boards accumulate under this name across commits, which is what makes a later comparison possible. |
source_kind |
inferred | kicad or gerber. |
fail_on |
critical |
critical, warning, or none. |
api_base |
https://www.embeddedci.com |
|
timeout_seconds |
300 |
|
summary |
true |
Write the findings table to the job summary. |
Outputs run_id, board_id, project_id, critical, warning, info, and rules_json (a
path to the downloaded findings, for a step that wants the detail).
fail_on: critical is the default deliberately. Gating on warnings sounds stricter but is worse
in practice: a board of any real complexity carries warnings a human has already looked at and
accepted, and a check that cries wolf on every push gets switched off within the week.
A Gerber upload must include an IPC-D-356 netlist. Gerbers carry no net information, so without it there is no way to tell a signal trace from its own ground pour — the server refuses the upload rather than analysing something meaningless.
Implementation note: a composite action calling the EMI REST API with curl and jq, both
already on every runner. Unlike upload-artifact there is no SDK to wrap — nothing else
implements this flow — so there is no second copy to drift, and no build step between editing
emi/emi-analyze.sh and the change taking effect.