# Your first feedback loop in Claude Code

One way to wire the earlier Claude Code pieces into a single unattended loop, from the agent-ready label to a reviewed pull request and a plugin update.

Source: https://ai-sw-factory.mellicci.dev/fundamentals/your-first-feedback-loop/claude-code

This page shows one way to assemble the loop, not a recommended pipeline. It adds no new primitives; it only shows the glue between the pages linked in the tables.

## At a glance

| | Claude Code |
|---|---|
| Term | No single feature: a `claude -p` run that loads a plugin, a goal, a `Stop` hook and a reviewer subagent |
| Configured in | `.github/workflows/agent-ready.yml`, `.claude/ci-settings.json`, the `ticket-service-kit` plugin, `CLAUDE.md` |
| Loads / runs | Once per `agent-ready` label; under `--bare` nothing loads unless a flag names it |
| Scope & precedence | `--settings` file and flags for this run; plugin components are namespaced `ticket-service-kit:` |

## Build the scenario

Everything below already exists on the linked pages. The files involved:

```text
ticket-service/
├── .github/workflows/agent-ready.yml
├── .devcontainer/devcontainer.json
├── .claude/
│   ├── ci-settings.json          permissions, sandbox, Stop hook
│   └── hooks/round-gate.sh       Stop hook, max 10 rounds
├── CLAUDE.md
├── src/db/CLAUDE.md
└── .agent/logs/tool-calls.jsonl  written at runtime, git-ignored
acme/agent-marketplace/plugins/ticket-service-kit/
├── .claude-plugin/plugin.json    "version": bumped per learning
├── skills/add-migration/   agents/reviewer.md
├── hooks/hooks.json        scripts/{format,guard,log}.sh
└── .mcp.json                     github server
```

The agent step, run inside the devcontainer (the documentation doesn't show how a GitHub Actions job enters one; wire that yourself). The marketplace repo is checked out to `kit/`:

```yaml
- name: Run agent
  env:
    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
    GITHUB_MCP_TOKEN: ${{ secrets.AGENT_GITHUB_TOKEN }}
    CLAUDE_CODE_STOP_HOOK_BLOCK_CAP: 12
    PROMPT: |
      /goal pnpm test exits 0, shown in your output. Implement issue #${{ github.event.issue.number }}
      (read it with the github tool). Use /ticket-service-kit:add-migration for schema changes.
      Before finishing, run the ticket-service-kit:reviewer subagent; fix every failed criterion
      and review again. Stop after 10 rounds. End with the reviewer's verdict.
  run: |
    mkdir -p .agent && touch .agent/loop-active
    claude --bare -p "$PROMPT" --settings .claude/ci-settings.json \
      --append-system-prompt-file CLAUDE.md --plugin-dir kit/plugins/ticket-service-kit \
      --allowedTools "mcp__plugin_ticket-service-kit_github__get_*" \
      --max-turns 60 --max-budget-usd 10 --output-format json > "$RUNNER_TEMP/result.json"
```

## Specifics

### The pieces and where they live

| Piece | File | Loads / runs when | Page |
|---|---|---|---|
| Trigger, exit handling, PR | `agent-ready.yml` | Label `agent-ready`; the workflow, not the model, pushes | [Headless execution](https://ai-sw-factory.mellicci.dev/fundamentals/headless-execution/claude-code) |
| Container, firewall | `.devcontainer/devcontainer.json` | The job starts the run inside it | [Sandboxing](https://ai-sw-factory.mellicci.dev/fundamentals/sandboxing/claude-code) |
| CI baseline | `.claude/ci-settings.json` | `--settings`; `dontAsk`, sandbox on | [Security model](https://ai-sw-factory.mellicci.dev/fundamentals/security-model/claude-code) |
| Instruction files | `CLAUDE.md`, `src/db/CLAUDE.md` | Root file via `--append-system-prompt-file`; see gotchas | [Instruction files](https://ai-sw-factory.mellicci.dev/fundamentals/instruction-files/claude-code) |
| Skill | `skills/add-migration/` | When the prompt or the model invokes it | [Skills](https://ai-sw-factory.mellicci.dev/fundamentals/skills/claude-code) |
| MCP server | plugin `.mcp.json` | Connects at start; issue is read through it | [MCP](https://ai-sw-factory.mellicci.dev/fundamentals/mcp/claude-code) |
| Hooks | plugin `hooks.json` | Every edit and tool call | [Hooks](https://ai-sw-factory.mellicci.dev/fundamentals/hooks/claude-code) |
| Goal and round cap | prompt, `round-gate.sh` | After every turn | [Loops](https://ai-sw-factory.mellicci.dev/fundamentals/loops/claude-code) |
| Reviewer | `agents/reviewer.md` | Before the run finishes | [Subagents](https://ai-sw-factory.mellicci.dev/fundamentals/subagents/claude-code) |
| Distribution | `plugin.json`, marketplace | `--plugin-dir` in CI, installs for developers | [Plugins](https://ai-sw-factory.mellicci.dev/fundamentals/plugins/claude-code) |

### The CI run, end to end

| Step | Who acts | What happens |
|---|---|---|
| 1 | Maintainer | Adds the `agent-ready` label; the workflow starts |
| 2 | Workflow | Installs dependencies, checks out the kit, arms the gate, runs `claude -p` |
| 3 | Harness | Loads the settings file, `CLAUDE.md` text and the plugin named by flags |
| 4 | Model | Reads the issue with `mcp__plugin_ticket-service-kit_github__get_issue`, edits, runs `pnpm test` |
| 5 | Hooks | `guard.sh` blocks, `format.sh` formats, `log.sh` records each call |
| 6 | Reviewer | Returns pass or fail per criterion; the model fixes the failures |
| 7 | Workflow | Exit code 0 and a diff: push `agent/issue-N`, open the PR |
| 8 | Person | Reviews the PR; uploaded artifacts show how it got there |

### Gates and the reviewer

| Gate | Enforced by | Stops the run when |
|---|---|---|
| `guard.sh` | Harness, `PreToolUse` | A call matches a rule; the reason goes back to the model |
| `dontAsk` plus deny rules | Harness | A call isn't allowed; the run continues without it |
| `round-gate.sh` | Harness, `Stop` hook | Blocks stopping until `pnpm test` passes; lets go after round 10 |
| `--max-turns`, `--max-budget-usd` | Harness | Turn or spend cap; exits with an error |
| `/goal` clauses ("after 10 rounds") | A small evaluator model | Judged, not enforced |
| Reviewer subagent | Model, asked by the prompt | Nothing forces the call; the diff on the PR shows whether it happened |
| Pull request review | A person | Merge is a human decision |

### Feeding learnings back

| What you keep | Where | What a person does with it |
|---|---|---|
| Reviewer verdict and final message | `result.json` | Finds the criterion that failed most often |
| Tool-call log | `tool-calls.jsonl` | Sees the wrong command guessed in each run |
| Denials | `stream-json` output, if you switch to it | Sees calls the baseline refused |

```yaml
- uses: actions/upload-artifact@v4
  if: always()
  with: { name: agent-run, path: "${{ runner.temp }}/result.json\n.agent/logs/" }
```

A repeated failure becomes one edit: a line in `CLAUDE.md`, a step in the skill, a stricter `guard.sh`, a criterion in `reviewer.md`. Bump `version` in `plugin.json` and push; developers run `claude plugin update ticket-service-kit@acme-marketplace`. CI picks up the new files on the next checkout of `kit/`.

### What to check first when it fails

| Symptom | Piece | Where to look |
|---|---|---|
| Issue never read, tool calls fail | MCP | `/mcp` in a local session with `--plugin-dir`; tool names carry the `plugin_ticket-service-kit_` prefix |
| Model ignores a convention | Instruction files | `/context` under **Memory files**; under `--bare`, only the file you passed is there |
| Skill not offered | Skill | `/skills`; `claude plugin validate kit/plugins/ticket-service-kit` |
| Guard didn't block | Hooks | `/hooks`; a wrong path or exit 1 fails open |
| Run never stops, or stops early | Goal, `Stop` hook | `/goal` status locally; `.agent/loop-round`, the block cap variable |
| No verdict in the output | Reviewer, turn cap | `result.json`; reaching `--max-turns` leaves no `result` text |
| Expected edit missing, job green | Permissions | Denials under `dontAsk`; check the diff, not only the exit code |
| Log empty | Hooks | `log.sh` path and `.agent/logs/` in the artifact |

## Gotchas

- `--bare` skips `CLAUDE.md` discovery, so `src/db/CLAUDE.md` does not load in this run. Without `--bare`, instruction files, `.mcp.json` and project hooks load on their own, but project `permissions.allow` rules are unused and the repository's hooks run unreviewed.
- Nothing forces the reviewer call; the prompt only asks for it. For a guarantee, make the `Stop` hook check for evidence of a review before it lets go.
- The hook continuation cap is 8 by default; without `CLAUDE_CODE_STOP_HOOK_BLOCK_CAP` above your round limit, the harness overrides the gate early.
- The plugin's hooks and MCP server run outside the built-in sandbox. Only the container, firewall and a task-scoped token bound them.
- The documentation doesn't specify how `--max-turns` interacts with `/goal`, or whether `/goal` and a `--plugin-dir` plugin's hooks work under `--bare` in every case. Test the first run by reading the log.
