GRM-171: docs: add runner-ops, molecule-testing, vikunja-tasks skills, fix create-task docs
Post-merge / detect-and-configure (push) Successful in 50s
Post-merge / release-and-maintain (push) Successful in 3m6s

This commit was merged in pull request #278.
This commit is contained in:
2026-09-18 22:48:26 +00:00
parent f433b0980a
commit fe684bad50
10 changed files with 445 additions and 28 deletions
+13 -3
View File
@@ -2,18 +2,28 @@
Quick reference for devx tools when working on this repo.
## When to Invoke
Invoke this skill when creating PRs, checking CI status, adding
labels, rebasing branches, or performing any PR lifecycle operation.
## Prerequisites
- `.venv` exists (run `make setup` if not)
- `.env` with `DEVELOPER_GITEA_API_TOKEN`, `VIKUNJA_TOKEN`
## PR Workflow (use these, not raw git/tea/MCP)
| Task | Command |
|------|---------|
| Create Vikunja task | `make create-task -- --title "..." --description "..."` |
| Create Vikunja task | `.venv/bin/python -m devx.tools.create_task --title "..." --description "..."` (make target doesn't forward args) |
| Create PR | `make create-pr` |
| Push + create PR | `make push-with-pr` |
| Check CI status | `make devx-pr-status` or `make devx-pr-status PR=42 WAIT=1` |
| Fetch CI failure logs | `make devx-pr-logs` or `make devx-pr-logs PR=42 JOB=quality TAIL=50` |
| Add ready-to-merge label | `make devx-pr-label` or `make devx-pr-label PR=42` |
| Rebase current branch | `make rebase` |
| Rebase PR via API | `make pr-rebase` or `make pr-rebase PR=42` |
| Rebase current branch | `make devx-rebase` |
| Rebase PR via API | `make devx-pr-rebase` or `make pr-rebase PR=42` |
## Auto-merge Behavior
+70
View File
@@ -0,0 +1,70 @@
# molecule-testing
Authoring and debugging `gitea_runner` molecule scenarios. For running
tests use the `testing-and-debugging` make targets — this covers
writing scenarios and fixing DIND/platform issues.
## When to Invoke
- Adding a molecule scenario for the `gitea_runner` role
- A scenario fails on platform setup, DIND, or registration mocking
- Reviewing scenario coverage for a role change
## Prerequisites
- Docker running locally
- `.venv` exists (`make setup`)
## Scenario Layout
`ansible/roles/gitea_runner/molecule/<scenario>/`:
Current scenarios: `default`, `template-content`, `deregister`,
`multi-instance`, `update`, `remove`, `lifecycle`.
| File | Purpose |
|------|---------|
| `molecule.yml` | driver/platforms/provisioner config |
| `converge.yml` | applies the role |
| `verify.yml` | assertions scoped to the scenario |
| `prepare.yml` | optional host prep |
Scenario registration lives in `pyproject.toml` (scenario map used by
`devx.molecule` distribution in CI) — a new scenario MUST be
registered there or CI never runs it.
## molecule.yml Conventions
- Platform name/image/command are env-overridable via
`${MOLECULE_PLATFORM_*}` so all-platforms runs work.
- `remote_tmp: /tmp` in provisioner `config_options` — default temp
dir breaks in containers.
- `ANSIBLE_ROLES_PATH` must include the repo roles root.
- Use `inventory.group_vars` to isolate the scenario: disable
unrelated features rather than editing tasks.
- Runner registration in tests is mocked/faked — scenarios must not
require a live Gitea instance; check how existing scenarios stub
the registration/token flow before adding API calls.
## Debugging
```bash
cd ansible/roles/gitea_runner
molecule test -s <scenario>
molecule converge -s <scenario>
molecule login -s <scenario>
```
- "Failed to create temporary directory" → `remote_tmp: /tmp` missing.
- Idempotence failures → find the changed task on second converge.
- Registration/API timeouts → the scenario hit a real endpoint —
stub it like the existing scenarios do.
## Common Mistakes
- Adding a scenario without registering it in `pyproject.toml` —
silently untested.
- Hardcoding the platform image — keep `${MOLECULE_PLATFORM_*}`
overrides.
- Calling the real Gitea API in converge — scenarios must be
self-contained; mock the registration path.
+73
View File
@@ -0,0 +1,73 @@
# runner-ops
Operating the Gitea Actions runner fleet: registration lifecycle,
stale-runner cleanup, image pruning, and safe debugging. Core code:
`src/grm/runner_manager.py`, `src/grm/executor.py`,
`src/grm/registry.py`.
## When to Invoke
- Runners go offline, stall, or pile up stale registrations
- Runner hosts need install/update/remove/deregister operations
- Disk pressure on runner hosts (image/container accumulation)
- Working on S08 (leases, physical-host admission, disk watermarks)
## Prerequisites
- `.env` with Gitea admin token for API operations
- SSH access to runner hosts for Ansible-driven lifecycle
- Runner registrations visible via admin API:
`GET /api/v1/admin/actions/runners`
## Architecture
- `RunnerManager` orchestrates install/update/lifecycle via
`AnsibleExecutor` against the `gitea_runner` role; `RunnerRegistry`
tracks local runner state.
- Runners execute jobs in Docker (`docker` label) — every job gets a
fresh container from `ci-base`/`ci-quality`/`ci-full` images.
- Molecule jobs nest containers (DIND) — privileged, `SYS_ADMIN`,
`/var/lib/docker` volume.
## Lifecycle Operations
| Task | Entry point |
|------|-------------|
| Install/update runners | `grm` CLI → `RunnerManager` (Ansible) |
| Stale registration cleanup | `scripts/cleanup_stale_runners.py` — deletes runners offline >1h via `DELETE /api/v1/admin/actions/runners/{id}` |
| Image pruning | `scripts/prune_runner_images.py` — reclaims disk from old CI image versions |
Stale registrations accumulate when a host is rebuilt, re-registered,
or its runner process dies unrecoverably — clean them before capacity
accounting.
## Debugging a Stuck Runner
1. Check registration state via admin API (offline vs online).
2. SSH to the host: `systemctl status` the runner service / inspect
`docker ps` for orphaned job containers.
3. Orphaned molecule containers: safe to remove ONLY when no molecule
run is active — check runner logs first (`runner-ops` counterpart
of "don't force-remove active containers", fixed in GRM-166/167).
4. Disk pressure: check `/var/lib/docker` usage, then
`prune_runner_images.py` — never blanket `docker system prune`
while jobs may be mid-flight.
## S08-Relevant Rules
- Runner admission must be per physical host — a runner that shares
hardware must declare capacity, not just labels.
- Cleanup must never remove a container a live job owns — ownership
check before any force-removal.
- Disk watermark logic belongs in the role/scripts, not ad-hoc
cron `docker prune`.
## Common Mistakes
- `docker system prune -a` on a runner host — kills in-flight job
containers and image cache mid-run.
- Deleting an offline runner registration while the host still runs
the service — it re-registers and duplicates; stop the service
first.
- Treating molecule DIND containers as junk — they belong to an
active scenario; check timestamps and runner logs.
@@ -1,5 +1,14 @@
# Spec-Driven Development
## When to Invoke
Invoke this skill when starting any change — every PR requires a spec
at `docs/specs/<TASK-ID>.md` that CI validates before merge.
## Prerequisites
- A Vikunja task ID (`GRM-N`) — see `vikunja-tasks` skill
## Overview
Every change starts with a spec. No spec, no code. No code, no PR.
+18 -8
View File
@@ -3,6 +3,17 @@
Make targets for testing, debugging, and CI investigation. **Use these
instead of raw `pytest`, `ruff`, or `molecule` commands.**
## When to Invoke
Invoke this skill when running tests, investigating CI failures, or
debugging molecule scenarios. Also invoke when asked to "run tests",
"check coverage", or "debug a failure".
## Prerequisites
- `.venv` exists (run `make setup` if not)
- For molecule tests: Docker is running
## Why Make Targets
Make targets encapsulate the correct venv activation, PYTHONPATH, env
@@ -31,9 +42,8 @@ produces false failures (missing dependencies, wrong Python version).
| Task | Command | Notes |
|------|---------|-------|
| All scenarios | `make molecule` | All 6 scenarios on Ubuntu 22.04 |
| All platforms | `make molecule-all` | All 6 scenarios on all 4 OSes |
| Parallel | `make molecule-all-parallel` | MOLECULE_JOBS=4 |
| All scenarios | `make molecule` | All 7 scenarios on Ubuntu 22.04 |
| All platforms | `make molecule-all` | All 7 scenarios on all 4 OSes |
### Spec-Driven Workflow
@@ -46,12 +56,12 @@ CI validates the spec before running expensive jobs.
**Before pushing any branch:**
```bash
make pre-push
make lint-all && make pytest-cov
```
This runs `lint-all` + `pytest-cov`. The pre-push git hook only
validates the Vikunja task exists — it does NOT run tests. You must
run `make pre-push` manually.
This runs all linters + unit tests with coverage. The pre-push git
hook only validates the Vikunja task exists — it does NOT run tests.
Run the checks manually (there is no `pre-push` target here).
## CI Failure Investigation
@@ -59,7 +69,7 @@ When investigating a CI failure:
1. **Fetch logs via MCP** — use `mcp_call_tool` with gitea server,
`actions_run_read` method, `download_job_log` tool
2. **Reproduce locally** — use `make pytest-cov` or `make lint-ci`
2. **Reproduce locally** — use `make pytest-cov` or `make lint-all`
depending on which CI job failed
3. **Never run raw pytest** — always use the make target
+74
View File
@@ -0,0 +1,74 @@
# vikunja-tasks
Vikunja task lifecycle beyond `create`: querying status, closing, and
recovering when the tracker is unreachable.
## When to Invoke
- Creating, closing, or checking a Vikunja task
- A spec workflow step needs the task ID or done state
- `vikunja.oblachno.oblachno.fyi` fails to resolve / times out
## Prerequisites
- `.env` with `VIKUNJA_TOKEN`
- Project ID comes from `[tool.devx]` in `pyproject.toml`
(`DEVX_VIKUNJA_PROJECT_ID`)
## Create
`make create-task` does **not** forward arguments — call the module:
```bash
.venv/bin/python -m devx.tools.create_task \
--title "Task title (no GRM-N prefix)" \
--description "<h2>Context</h2><p>...</p>"
```
Prints `GRM-N` + next steps. Title must not include the task-ID
prefix (auto-merge prepends it; a manual prefix double-prefixes the
PR title and fails validation).
## Query / Close
```bash
# Task details (ID = numeric part of GRM-N)
curl -sf -H "Authorization: Bearer $VIKUNJA_TOKEN" \
"https://vikunja.oblachno.oblachno.fyi/api/v1/tasks/<N>"
# Close: mark done
curl -sf -X POST -H "Authorization: Bearer $VIKUNJA_TOKEN" \
-H "Content-Type: application/json" -d '{"done":true}' \
"https://vikunja.oblachno.oblachno.fyi/api/v1/tasks/<N>"
```
Post-merge automation marks the task done when the PR squash-merges —
manual close is only needed for abandoned/superseded tasks.
## Task-ID / Spec Collisions
Vikunja IDs can collide with historical spec files (an old task reused
the number). Convention: preserve the old file as
`docs/specs/<ID>-<topic>-historical.md`, then write the new spec at
`docs/specs/<ID>.md`. Check `git log` on the existing spec before
moving it.
## Tracker Unreachable
If the Vikunja host fails DNS/TLS:
1. Don't block the whole workflow — record the intended task title in
the spec draft and retry `create_task` before branching.
2. Never invent an ID — branch/PR titles must match a real task or
`pre_push_check` / auto-merge validation fails.
3. DNS failures observed so far were transient; retry after a few
minutes before escalating.
## Common Mistakes
- `make create-task -- --title ...` — args are dropped; use the module
call above (forwarding fix is S11 scope).
- Including `GRM-N:` in the task title — double prefix breaks
auto-merge.
- Closing a task whose PR is still open — auto-merge's post-merge
step handles the close; manual close confuses the audit trail.