When the task needs its own workflow
Kadu's default execution runs the skill on a runner with Node, jq and
Python. A task that depends on a tool the runner does not bring, on a known
egress IP or on its own runner needs a GitHub Actions workflow in the
repository itself.
Kobana's case is the security workspace: security-scan.yml installs the
pentest suite, schedules the weekly and monthly scan and runs on a runner with
a fixed egress IP. The template to copy is the same repository's
wh-run-skill.yml: Playbook checkout, Claude Code installation, a stub of
the helpers in /tmp/agent_state.sh, claude -p "/<plugin>:<skill>" --plugin-dir
and set +e around the pipeline.
Two execution paths​
The task now has two paths, and what differs between them appears nowhere unless it is written down:
| Run by Kadu | Own workflow | |
|---|---|---|
| Runner | Kadu's, without the tools | the workflow's, with the tools installed |
| Variables | only what is in required_secrets | only what the step's env: block maps from secrets.*/vars.* |
| Instrumentation | the real console | the stub, which only echoes in the run log |
| Output files | $OUTPUT_FILES_DIR, published by Kadu | the workflow's own upload-artifact |
Two consequences:
- Every configuration variable goes in both places: in
required_secrets, for Kadu, and in the workflow'senv:, for the schedule. Forgetting one side makes the configuration apply on one path and fall back to the default on the other. - A missing tool becomes a pending item, not an error. A run from the console arrives without the tools: the skill records what was missing and moves on. Whoever needs the full execution is the workflow.
A workflow that generates traffic is born closed​
A workflow that generates traffic against third parties or against production
is born fail-closed: without the authorization variable
(vars.SECURITY_SCAN_AUTORIZADO=true, in Kobana's case), the job ends green
with a ::notice and does nothing. A cron that is red every week because of
missing configuration becomes noise nobody reads.