원클릭으로
opsx-apply-loop
Iteratively run apply→verify in a loop until verify passes, then auto-archive — runs per-app in Docker context
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Iteratively run apply→verify in a loop until verify passes, then auto-archive — runs per-app in Docker context
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Reset the OpenRegister development environment (stop, remove volumes, restart, install apps)
Create a Pull Request from the current branch — runs local checks, picks target branch, and opens the PR on GitHub
Analyze a project's OpenSpec from 8 persona perspectives and suggest additional features
Detect admin settings Vue components registered in the vue-router. Admin settings are rendered by Nextcloud's settings framework via `AdminSettings.php`; adding their Vue components to the in-app router exposes them as publicly-accessible frontend routes, bypassing all server-side access checks. ADR-004 hard rule. Observed 2026-04-30 on doriath where `/settings → AdminRoot` was a route in `src/router/index.js` (commit c7c72e9).
Run `composer audit` to check composer.lock dependencies for known CVEs. Invoked by the builder before push and the reviewer's mandatory block. Mirrors the orchestrator's `composer-audit` quality gate.
Scan lib/ for forbidden debug helpers (var_dump / die / error_log / print_r / dd / dump) that should not ship. Invoked by the builder before push, by the reviewer as Mandatory Step 2, and by the fixer during a retry. Mirrors the orchestrator's `forbidden-patterns` quality gate.
| name | opsx-apply-loop |
| description | Iteratively run apply→verify in a loop until verify passes, then auto-archive — runs per-app in Docker context |
| metadata | {"category":"Workflow","tags":["workflow","automated","loop","docker","experimental"]} |
Check the active model from your system context (it appears as "You are powered by the model named…").
"This command requires Sonnet or Opus — the apply→verify loop needs strong reasoning to implement tasks and evaluate verification results. Please switch to Sonnet (
/model sonnet) or Opus (/model opus) and re-run."
Check container authentication — follow the procedure in references/container-auth-check.md. Stop immediately if no credentials are found.
AUTONOMOUS MODE — This skill is a fully automated orchestrator. The standard CLAUDE.md workflow (ask clarifying questions → present plan → wait for approval) does NOT apply here. Do NOT pause between steps to ask for confirmation or approval unless a step explicitly says to use AskUserQuestion. Proceed through all steps automatically. When an inline skill completes and returns control, immediately continue to the next numbered step without waiting.
Automated orchestrator: runs opsx-apply → opsx-verify in a loop until verify is clean, optionally runs targeted tests (on host), then runs opsx-archive. The apply→verify loop runs inside an isolated Docker container (Claude CLI + app files only, no git, no GitHub). Tests run outside the container. Host handles testing, archive, branch creation, GitHub sync, and git commits.
Each app in this workspace has its own git repository. The container mounts the app's directory and a read-only copy of the shared .claude/ skills. Nextcloud containers must be running on the host for environment checks and post-container testing.
[host] issue check → branch (from development) → container start (app dir + .claude skills)
[container] apply → verify → loop (max 5) → verify-clean → exit
[host] test loop (optional, max 3) → deferred tests (optional, once) → archive (once) → git commit → github sync
Input: Optionally specify <app> <change-name> (e.g., /opsx-apply-loop procest add-sla-tracking). If omitted, prompt for app and change.
If both app and change name are provided, use them directly.
If only one argument is provided, treat it as the change name and scan all apps for a match.
Otherwise, scan all app directories for active changes and use AskUserQuestion to let the user select:
# Scan for active changes across all apps
for app in procest pipelinq openregister opencatalogi docudesk mydash nldesign openconnector softwarecatalog zaakafhandelapp openklant larpingapp planix; do
if [ -d "$app/openspec/changes" ]; then
for change_dir in $app/openspec/changes/*/; do
if [ -f "${change_dir}tasks.md" ] && [[ "$change_dir" != *"/archive/"* ]]; then
echo "$app: $(basename $change_dir)"
fi
done
fi
done
Ask the user to select from the list. Do not auto-select.
Store as {APP} and {CHANGE_NAME}. All subsequent file paths use {APP}/openspec/changes/{CHANGE_NAME}/.
Always announce: "Using change: <app>/<change-name>" and how to override.
Check whether a GitHub tracking issue already exists for this change:
cat {APP}/openspec/changes/{CHANGE_NAME}/plan.json 2>/dev/null | grep -q '"tracking_issue"'
If plan.json exists and tracking_issue is set: log ✅ GitHub issue #<N> already exists and proceed. Store as {ISSUE_NUMBER}.
If plan.json is missing or has no tracking_issue:
⚠️ No GitHub tracking issue found — running opsx-plan-to-issues firstopsx-plan-to-issues skill for {CHANGE_NAME} with this explicit context passed to it: "Invoked from apply-loop — skip Step 6 AskUserQuestion and return control to apply-loop after completing."| Prompt from opsx-plan-to-issues | Answer |
|---|---|
| "Which change(s) should I create GitHub issues for?" | Select {CHANGE_NAME} |
"Create these N issue(s) in <owner/repo>?" | Yes, create all |
The repo is determined from the app's project.md table (GitHub Repo column) or git remote get-url origin inside the app directory.
When plan-to-issues completes (look for its summary output or "plan.json saved"), immediately and automatically continue to Step 3 — do NOT pause, do NOT ask the user anything, do NOT wait for confirmation. You are in autonomous mode.
After plan-to-issues completes, verify plan.json now contains a tracking_issue. Store as {ISSUE_NUMBER}.
Each app is its own git repository. All git operations run from the app directory.
cd {APP}
# First check if the feature branch already exists locally
git fetch origin
git branch --list "feature/{ISSUE_NUMBER}/{CHANGE_NAME}"
If the branch already exists (e.g., resuming a previous run):
feature/{ISSUE_NUMBER}/{CHANGE_NAME} already exists. Resume work on it or reset it?"
git checkout feature/{ISSUE_NUMBER}/{CHANGE_NAME} (skip development checkout/pull)git branch -D feature/{ISSUE_NUMBER}/{CHANGE_NAME}, then follow the "If the branch does not exist" flow right belowIf the branch does not exist (or after reset):
# Only checkout and pull development when we need to create a new branch
git checkout development
git pull origin development
# Branch follows the convention: feature/<issue-number>/<change-name>
git checkout -b feature/{ISSUE_NUMBER}/{CHANGE_NAME}
Log: ✅ On branch feature/{ISSUE_NUMBER}/{CHANGE_NAME} in {APP}/
Silently read the test-plan before asking the user anything. This feeds the test cycle option in Step 5.
Check if {APP}/openspec/changes/{CHANGE_NAME}/test-plan.md exists.
If it exists: read all test command field values, deduplicate, then classify each:
| Fits in loop? | Commands | Reason |
|---|---|---|
| Yes | /test-functional | Single agent, uses Playwright on host against live Nextcloud — tests GIVEN/WHEN/THEN from specs |
| Yes | /test-api | Single agent, REST API and ZGW compliance via curl — text output, no browser needed |
| Yes | /test-security | Single agent, uses Playwright on host — include only if change touches auth, roles, or permissions |
| Yes | /test-accessibility | Single agent, uses Playwright on host to inject axe-core — include only if change touches frontend UI |
| No (deferred) | /test-counsel | 8 parallel agents |
| No (deferred) | /test-app | Multi-agent or full-app sweep |
| No (deferred) | /test-persona-* | Too broad, not change-specific |
| No (deferred) | /test-regression, /test-performance | Cross-feature or non-blocking |
Rules:
/test-persona-*) that appears in the test-plan → replace with /test-functional in {TEST_COMMANDS_IN_LOOP} (same coverage, single agent){TEST_COMMANDS_IN_LOOP} = [/test-functional]Store:
{TEST_COMMANDS_IN_LOOP} — the filtered commands to run in the automated test loop{TEST_COMMANDS_DEFERRED} — the excluded commands to surface at the end (Step 13){TEST_PLAN_EXISTS} — true/falseUse AskUserQuestion to ask:
"Ready to run
opsx-apply-loopfor{APP}/{CHANGE_NAME}?Branch:
feature/{ISSUE_NUMBER}/{CHANGE_NAME}in{APP}/GitHub issue: #{ISSUE_NUMBER}Apply→verify loop (inside isolated Docker container):
- Max 5 iterations; CRITICAL issues stop the loop; warnings-only proceeds to archive
Optional: test cycle (outside container, requires running Nextcloud environment):
- After verify is clean, runs:
<list TEST_COMMANDS_IN_LOOP, or 'test-functional (default)' if no test-plan>- Max 3 test iterations; if tests fail, loops back into apply→verify
- ⚠️ These tests run on your host using the live Nextcloud app — NOT inside the container"
Options:
{TESTS_ENABLED}=true){TESTS_ENABLED}=false)Read references/container-setup-guide.md for the full procedure: creating the log folder (6.1), scanning prior run history (6.2), version checks and image build (6.3), network creation (6.4), container startup with prompt construction (6.5, including the full startup prompt text), monitoring loop (6.6), and container exit handling with all 5 scenarios (6.7). Apply all sub-steps now.
Steps 7–8 execute inside the container by the container's Claude CLI session. The startup prompt (in references/container-setup-guide.md Step 6.5) directs the container to read references/apply-verify-loop-protocol.md.
The steps below (9–15) execute on the host, after the container has exited.
Skip this step entirely if {TESTS_ENABLED}=false. Proceed directly to Step 10.
Read references/host-test-loop-protocol.md for the full protocol: Nextcloud environment check, in-loop test command execution via Agent tool (9a), test result evaluation (9b), and container re-entry for test-failure fixes (9c). Apply the full procedure now.
Skip this step if any of these are true:
{TESTS_ENABLED}=false{TEST_COMMANDS_DEFERRED} is emptyIf applicable, use AskUserQuestion to ask:
"The following test commands were in your test-plan but were not included in the automated loop (multi-agent or broad-scope):
<list {TEST_COMMANDS_DEFERRED} with reason each was excluded>
Would you like to run these now? If any fail, one final apply→verify cycle will run to address the findings before archiving."
Options:
If yes:
Use the Agent tool (NOT the Skill tool) to run each command in {TEST_COMMANDS_DEFERRED} sequentially, exactly as described in Step 9a — construct the skill file path, launch a general-purpose Agent in READ-ONLY mode, and read its structured result line. Run all commands once (not looped). After each Agent returns, immediately continue to the next — do NOT pause between commands.
Note — reduced parallelism: Sub-agents spawned via the Agent tool do not have access to the Agent tool themselves, so multi-agent skills like
/test-counsel(which normally runs 8 persona agents in parallel) will run sequentially instead. Coverage is the same; it just takes longer. This is expected and acceptable for the deferred test pass.
If all pass: log ✅ Deferred tests all passed — immediately and automatically continue to Step 11 (archive).
If any fail:
⚠️ Deferred test failures found — running one final apply→verify cycleFAIL_TIME=$(date +%H:%M) then write to ${LOG_DIR}/apply-loop-${FAIL_TIME}-test-failures-${TEST_ITERATION}.logSTATUS (report exhaustion if needed, but archive once)Use the Agent tool (NOT the Skill tool) to run opsx-archive. The Agent tool runs as a subprocess and returns results directly — this is what allows the orchestrator to continue to Steps 12–15 after archiving. Using the Skill tool inline would terminate the conversation instead of returning control.
Construct the skill file path:
CLAUDE_SKILLS="$(cd {APP}/.. && pwd)/.claude/skills"
SKILL_FILE="${CLAUDE_SKILLS}/opsx-archive/SKILL.md"
Launch a general-purpose Agent with a prompt that includes:
{SKILL_FILE}."{CHANGE_NAME}. Working directory: $(pwd)/{APP}/."| Prompt from opsx-archive | Answer |
|---|---|
| "Sync delta specs first?" | Sync now |
| "Convert test cases to test scenarios?" (step 4.5) | Skip — apply-loop asks after all loops finish (Step 14) |
| "Close GitHub issue #N?" | No, leave it open |
ARCHIVE_RESULT: DONE ARCHIVE_PATH: <path>. Output nothing after the result line."The archive skill handles: artifact completion check, delta spec sync, spec link updates in main specs, docs/features/ updates, and CHANGELOG.md.
When the Agent returns, extract {ARCHIVE_PATH} from the result line.
Immediately and automatically continue to Step 12 — do NOT pause or wait for user input.
Log: 📦 Change archived
Commit all changes — implementation, test fixes, and archive artifacts — in one commit. Run from the app directory:
cd {APP}
git add .
git status # review what changed
git commit -m "feat: implement {CHANGE_NAME}
Co-Authored-By: Claude Sonnet 4.7 <noreply@anthropic.com>"
Log: ✅ Changes committed to feature/{ISSUE_NUMBER}/{CHANGE_NAME} in {APP}/
The container skipped all GitHub operations. Run them now from the host using the gh CLI.
13a. Final checkbox sync — verify the issue reflects the fully archived state of tasks.md. Earlier syncs (Step 6.7 Scenario A and Step 9c) updated checkboxes from the pre-archive location; this final sync reads the archived copy to ensure nothing was missed:
{APP}/openspec/changes/archive/YYYY-MM-DD-{CHANGE_NAME}/tasks.md (archived location)[x], ensure the corresponding checkbox is checked in issue #{ISSUE_NUMBER} — update any that are still unchecked:
get_issue → find any remaining - [ ] task lines → change to - [x] → update_issue (single call)gh issue view {ISSUE_NUMBER} --repo <owner/app> --json body --jq '.body' → update checkboxes → gh issue edit {ISSUE_NUMBER} --repo <owner/app> --body "<updated>"13b. Add a completion comment to the issue:
add_issue_comment → {owner, repo, issue_number: {ISSUE_NUMBER}, body: "✓ apply-loop complete — all tasks implemented and verified. Change archived to {APP}/openspec/changes/archive/YYYY-MM-DD-{CHANGE_NAME}/"}gh issue comment {ISSUE_NUMBER} --repo <owner/app> --body "..."13c. Ask about closing the issue using AskUserQuestion:
"Close GitHub issue #{ISSUE_NUMBER}?"
Options:
gh issue close {ISSUE_NUMBER} --repo <owner/app>Log: ✅ GitHub issue synced
Check if {APP}/openspec/changes/archive/YYYY-MM-DD-{CHANGE_NAME}/test-plan.md exists.
If test-plan.md exists, use AskUserQuestion to ask:
"The change had a test-plan.md. Convert test cases to reusable test scenarios?
Test scenarios are picked up automatically by
/test-counsel,/test-app, and persona test commands."
Options:
If test-plan.md does not exist: skip silently.
Before composing the report, read all log files in ${LOG_DIR} (ls -t "${LOG_DIR}"/) to ensure the summary reflects everything that actually happened across all container runs and test iterations. Read each file:
apply-loop-*-result.log files — collect STATUS, ITERATIONS, WARNINGS_ONLY from each runapply-loop-*-container.log files — skim for apply/verify outcomes per iterationapply-loop-*-test-failures-*.log files — note which test iterations failed and what was reportedReconcile any discrepancies between the in-memory log and the file contents — the files are authoritative.
Deliver the final report using the template in references/loop-report-template.md. Fill in all placeholders: loop log entries, summary table, pass/fail counts, remaining issues list.
Then use AskUserQuestion to ask: "What would you like to do next?"
Options:
/create-pr) — open a pull request from feature/{ISSUE_NUMBER}/{CHANGE_NAME} in {APP}//sync-docs app {APP}) — update {APP}/docs/ to reflect the new feature/sync-docs dev) — update .claude/docs//opsx-new)See references/container-limitations.md for the full table of what the container cannot do (and which host step handles each), container volume mappings, and the optional iptables network restriction to api.anthropic.com.
After execution, review what happened and append new observations to learnings.md under the appropriate section:
Each entry must include today's date. One insight per bullet. Skip if nothing new was learned.
opsx-apply, opsx-verify, opsx-archive, and the test commands{APP}/; never across app boundarieshydra/.claude/logs/ via the third volume mount (gitignored); the host reads those on exitfeature/<issue-number>/<change-name> to match the opsx-pipeline convention used across this workspace💡 If you switched models to run this command, don't forget to switch back to your preferred model with
/model <name>(e.g./model defaultor/model sonnet) when done.