Compare commits

...
Author SHA1 Message Date
ffaerber 85be0a3288 Merge pull request 'Let caller-provided skills receive their secrets again' (#125) from ai/caller-skill-secrets into main
ci / lint (push) Successful in 14s
Reviewed-on: #125
2026-09-15 09:46:59 +02:00
Felix FaerberandClaude Opus 5 e29b8141f8 Let caller-provided skills receive their secrets again
ci / lint (pull_request) Successful in 17s
#122 gave the reusable workflow an explicit workflow_call secret contract so
XAI_API_KEY would stop arriving empty. That fixed the model key and quietly
broke every caller-provided skill: SECRETS_JSON is toJSON(secrets), so it now
contains only the secrets this contract declares, and a skill's setup.sh finds
nothing under the names its own repo uses.

homelab's node1-ssh skill is the live casualty. Its setup.sh reads SWARM_HOST,
SWARM_USER and SSH_PRIV_KEY_B64, takes the "not set -- skipping node1 SSH alias"
path, and exits 0; the agent then loads the skill, is told it may run
`ssh node1 ...`, and gets nothing. @senior hit exactly this while diagnosing the
immich crash-loop in homelab #301 and had to fall back to a blind version
revert. The failure is silent by design -- the skip is what lets a fork run
without deploy secrets -- so nothing in the run says the capability is gone.

Declaring the three optional here is the smallest fix that keeps the contract.
It is not a general one: secret names for caller skills are by definition known
only to the caller, and an explicit contract cannot express that, so each new
caller-skill secret has to be added alongside these.

Also fixes two pieces of drift that would spread the same breakage: the header
still told callers to use `secrets: inherit`, and README's copy-me stub still
showed it, so the next repo onboarded would have arrived with no model key.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CYFnHida4AdiNDek2pzgXx
2026-09-15 10:09:19 +03:00
hermes 04b15a2c27 fix(agents): wire XAI_API_KEY through reusable workflow (#122)
ci / lint (push) Successful in 14s
Declare workflow_call secrets, diagnostics, explicit caller template.
2026-07-30 15:47:35 +02:00
hermes 9dc1c203cb fix(agents): declare workflow_call secrets + diagnose empty XAI_API_KEY
ci / lint (pull_request) Successful in 14s
Cross-owner reusable calls left XAI_API_KEY empty in the runner while
OLLAMA_CLOUD_API_KEY worked. Declare secrets on workflow_call, accept
alternate secret names, log key lengths (not values), and ship an
ai-agent.yml caller template with an explicit secrets map.
2026-07-30 16:47:13 +03:00
hermes 58db2996ad feat(agents): automatic Ollama Cloud → xAI fallback (#121)
ci / lint (push) Successful in 14s
Primary stays ollama-cloud; on quota/provider failure run-agent fails over once to agents.json fallback (xAI).
2026-07-30 15:28:44 +02:00
4 changed files with 75 additions and 5 deletions

No files matched your search

+42 -3
View File
@@ -1,9 +1,46 @@
name: agent
# Reusable AI-agent workflow, shared across repos. A caller repo triggers on issue_comment/issues
# and invokes this via: uses: gitea/agents/.gitea/workflows/agent.yml@main (secrets: inherit).
# and invokes this via: uses: gitea/agents/.gitea/workflows/agent.yml@main (explicit secret map,
# see .gitea/workflows/ai-agent.yml for the stub every caller copies).
# The gate + steps run in the caller's event context (github.event.* / github.repository are the caller's).
on:
workflow_call:
# Explicit secret contract so callers can map secrets by name (more reliable than
# secrets: inherit alone on some Gitea versions / cross-owner reusable workflows).
secrets:
GITEA_TOKEN:
required: true
OLLAMA_URL:
required: false
OLLAMA_CLOUD_API_KEY:
required: false
XAI_API_KEY:
required: false
TOKEN_PM:
required: false
TOKEN_SENIOR:
required: false
TOKEN_JUNIOR:
required: false
TOKEN_LEAD:
required: false
TOKEN_QA:
required: false
TOKEN_OPS:
required: false
TOKEN_INTERN:
required: false
# Secrets for caller-provided skills (.gitea/agent-skills/<name>/setup.sh). Their names are
# known only to the caller repo, but SECRETS_JSON below is toJSON(secrets) and can therefore
# only ever contain what this contract declares — so every such secret has to be listed here,
# optional, or the skill silently no-ops and the agent loses the capability without an error.
# These three are homelab's node1-ssh skill; add further caller-skill secrets alongside them.
SWARM_HOST:
required: false
SWARM_USER:
required: false
SSH_PRIV_KEY_B64:
required: false
# Pinned opencode version — used to install it and to key the CI cache below.
env:
@@ -150,7 +187,9 @@ jobs:
SCRIPTS: ${{ runner.temp }}/agents-scripts
OLLAMA_URL: ${{ secrets.OLLAMA_URL }}
OLLAMA_CLOUD_API_KEY: ${{ secrets.OLLAMA_CLOUD_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
# Accept common alternate names — empty XAI_API_KEY has bitten us when the
# secret was stored under a slightly different key on the caller repo.
XAI_API_KEY: ${{ secrets.XAI_API_KEY || secrets.XAI_KEY || secrets.GROK_API_KEY || secrets.XAI_TOKEN }}
NAME: ${{ steps.prep.outputs.name }}
SKILLS: ${{ steps.prep.outputs.skills }} # JSON array of skills this agent may load
run: bash "$SCRIPTS/install-opencode.sh"
@@ -213,7 +252,7 @@ jobs:
id: run
env:
SCRIPTS: ${{ runner.temp }}/agents-scripts
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY || secrets.XAI_KEY || secrets.GROK_API_KEY || secrets.XAI_TOKEN }}
# SELF_TOKEN = the RUNNING agent's OWN token (TOKEN_PM for @pm, TOKEN_OPS for @ops, …).
# Only this agent's token is placed in its process env, so no agent can act as another.
# Powers the gitea-api / gitea-admin skills — each agent calls Gitea as itself. Every
+19 -1
View File
@@ -12,4 +12,22 @@ on:
jobs:
agent:
uses: gitea/agents/.gitea/workflows/agent.yml@main
secrets: inherit
# Explicit secret map (plus inherit) so XAI_API_KEY / OLLAMA_* always reach the reusable
# workflow. secrets: inherit alone has left XAI_API_KEY empty on some Gitea cross-owner calls.
secrets:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
OLLAMA_URL: ${{ secrets.OLLAMA_URL }}
OLLAMA_CLOUD_API_KEY: ${{ secrets.OLLAMA_CLOUD_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
TOKEN_PM: ${{ secrets.TOKEN_PM }}
TOKEN_SENIOR: ${{ secrets.TOKEN_SENIOR }}
TOKEN_JUNIOR: ${{ secrets.TOKEN_JUNIOR }}
TOKEN_LEAD: ${{ secrets.TOKEN_LEAD }}
TOKEN_QA: ${{ secrets.TOKEN_QA }}
TOKEN_OPS: ${{ secrets.TOKEN_OPS }}
TOKEN_INTERN: ${{ secrets.TOKEN_INTERN }}
# Secrets for this repo's own caller-provided skills (.gitea/agent-skills/). Unset in a repo
# that ships no such skill, which is harmless: the skill's setup.sh no-ops on an empty value.
SWARM_HOST: ${{ secrets.SWARM_HOST }}
SWARM_USER: ${{ secrets.SWARM_USER }}
SSH_PRIV_KEY_B64: ${{ secrets.SSH_PRIV_KEY_B64 }}
@@ -5,6 +5,14 @@
# NAME SKILLS GITHUB_PATH HOME
set -eu
# Non-secret diagnostics — prove which provider keys reached the runner (length only).
echo "provider key lengths: OLLAMA_URL=${#OLLAMA_URL} OLLAMA_CLOUD_API_KEY=${#OLLAMA_CLOUD_API_KEY} XAI_API_KEY=${#XAI_API_KEY}"
if [ -z "${XAI_API_KEY:-}" ]; then
echo "WARNING: XAI_API_KEY is empty in this job. xai-oc fallback will fail."
echo "Fix: set Actions secret XAI_API_KEY on the CALLER repo (e.g. ffaerber/homelab),"
echo "not only on gitea/agents. Name must be exact: XAI_API_KEY"
fi
# PIN the opencode version: an unpinned `latest` means a breaking release (CLI flags, or the
# --format json event schema that build-activity-log.sh parses) breaks every agent in every repo
# at once. Bump deliberately by changing this default (or set OPENCODE_VERSION in the step env).
+6 -1
View File
@@ -87,7 +87,12 @@ on:
jobs:
agent:
uses: gitea/agents/.gitea/workflows/agent.yml@main
secrets: inherit
# Explicit map, not `secrets: inherit`: inherit alone has left XAI_API_KEY empty on some Gitea
# cross-owner calls. Copy the full list from .gitea/workflows/ai-agent.yml, which is the stub
# this file abbreviates -- a secret missing from the map does not reach the workflow at all.
secrets:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
# ... and the OLLAMA_*, XAI_API_KEY, TOKEN_* and caller-skill entries, verbatim.
```
That's the whole per-repo footprint, and it's the minimum a caller can be: the `on:` triggers must