Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions .claude/skills/start-app-server/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
name: start-app-server
description: "Start the django-react-intro app server in a Docker container and validate it responds. Uses a pre-validated recipe — no discovery, no guessing. Triggers on: 'spin up the server', 'run my app', 'start the server', 'verify app server', 'test my server', 'run app server', 'spin up the app'."
user_invocable: true
codepress_generated: true
---

# Start App Server — django-react-intro

Fast-path skill for starting django-react-intro's app server. All discovery was done during bootstrap on 2026-07-07T19:27:32Z — just execute.

The recipe lives at `.codepress/start-app-server/recipe.json`. This skill is the human-readable companion.

## Tools

These tools are pre-loaded — call them directly:

- `build_and_start_app_server` — build image + start container in one step
- `forward_app_request` — HTTP request into the running container
- `get_app_server_logs` — container stdout/stderr
- `stop_app_server` — clean up when done

## Static Context (from bootstrap)

- **Stack**: Two-app monorepo — Django 1.11 backend (`server/`, SQLite, Pipfile/Pipfile.lock) serving a Create React App frontend (`web/`, react-scripts 1.0.10) that's built and copied into `server/static/build` at image build time. Single container, no sibling app.
- **Dockerfile**: `Dockerfile`
- **Port**: 8000
- **Validation**: `GET /` → status in [200, 301, 302, 404]
- **Services**: none
- **Required secrets**: none

## Step 1: Drift Check

Verify the recipe hasn't gone stale:

```bash
git hash-object Dockerfile # compare to recipe.input_checksums.dockerfile (79ae4e91373124fd)
git hash-object server/Pipfile # compare to recipe.input_checksums.dependency_manifest (a4af380b1d34c482)
```

If either checksum mismatches, the file has been edited since bootstrap. Proceed anyway — the agent will catch any actual breakage at build time — but flag the drift in your final report so the user knows the recipe may need updating.

## Step 2: Fetch Secrets

This app needs no vault secrets. Skip to Step 3.

## Step 3: Build and Start

```text
build_and_start_app_server(
workspaceDir=<absolute path to repo root>,
port=8000,
dockerfilePath="Dockerfile",
envVars={},
)
```

If retrying after a fix in the same session, pass `existingContainerId=<previous id>` so the old container is stopped on a successful rebuild. On the first attempt there is no previous container — omit the argument.

## Step 4: Validate

If the tool returns `health_check: "ready"` → go to Step 5.

If it returns `health_check: "timed_out"`, poll up to 12 rounds × 5s each (default 12 rounds = 60 seconds; use `recipe.validation_max_polls` if set):

```text
for attempt in 1..12:
r = forward_app_request(containerId, path="/", method="GET")
if r.status in [200, 301, 302, 404]: break
sleep 5
```

If the poll is about to exhaust without success, check `get_app_server_logs` once. If the logs show a still-progressing startup step (running migrations, compiling assets, warming caches), extend with up to another 12 rounds before giving up. Apps with long migrations or asset builds commonly need 60–120 seconds.

Success = any status in [200, 301, 302, 404]. A definite crash in the logs, or true exhaustion after extension, counts as failure and jumps to "Repair on Failure" below.

## Step 5: Report

Tell the user:

- Container ID: `<id>`
- Port: 8000
- How to hit it: `forward_app_request(containerId, path="...")`
- How to stop: `stop_app_server(containerId)`

If the user only asked to start / spin up the server, **STOP HERE**. Do not proceed to verification unless they asked to verify or test changes.

## Known Fixes (from bootstrap)

These issues came up during the initial bootstrap — try these first if a rebuild is needed:

- web/package.json's postbuild script (`cp -r build/ ../server/static/build/`) fails if server/static doesn't exist yet — added `RUN mkdir -p /app/server/static` before `yarn build` in the frontend build stage.
- Container runs at runtime as uid/gid 65534 (nobody), but files land as root during the Docker build — SQLite couldn't create db.sqlite3 in /app/server. Added `RUN chown -R 65534:65534 /app/server` in the runtime stage.

## Repair on Failure

If `build_and_start_app_server` returns a genuine build/start error (not just a health timeout):

**Before attempting repair, check `recipe.repair_count`.** If it is already at the `>= 3` ceiling, do not attempt in-session repair — at three or more prior repairs the recipe has stopped converging on the customer's actual repo state, and another rebuild would just consume more agent sessions without resolving the underlying drift. Tell the user the recipe is likely stale and suggest they re-invoke `/codepress-bootstrap-app-server` to regenerate it from scratch, then stop.

Otherwise:

1. Read `get_app_server_logs` AND the tool's error string in full
2. Match the error against the Known Fixes list above — apply that fix first if it matches
3. Identify ALL issues in the error output — do not rebuild after fixing just one
4. Edit `Dockerfile` (and, if needed, `recipe.json` for port/services changes)
5. Rebuild with `existingContainerId` set to the previous container's ID
6. If still failing after 3 in-session retries:
- Bump `recipe.json.repair_count` by 1
- Set `recipe.json.origin` to `"repair"`
- Update `recipe.json.bootstrapped_at` to the current ISO timestamp
- Recompute `input_checksums` for the files you changed
- Append a one-line summary of what you fixed to `recipe.json.known_fixes`
- Also append that line to the "Known Fixes" section above in this SKILL.md
- Commit:
```bash
git add Dockerfile .codepress/start-app-server/recipe.json .claude/skills/start-app-server/
git commit -m "fix: update start-app-server recipe (repair attempt <N>)"
```
- In ECS mode, also push: `git push origin HEAD`

If you can't get it running after all that, stop the container if one is up, tell the user what you tried, and leave the recipe unchanged.

## Cleanup

**Do NOT stop a successfully started container.** The caller (user or `app-server-verify` Step 0) delegated here to leave a live server they can hit with `forward_app_request`. Leave it running and just report the container ID.

Only call `stop_app_server(containerId)` when:

- The user explicitly asks to stop or tear down the server, OR
- A build/start attempt failed and left a dead container behind (stop that failed container so it doesn't leak), OR
- You started a container as part of verification and the user asked to clean up afterward
23 changes: 23 additions & 0 deletions .codepress/start-app-server/recipe.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"schema_version": 1,
"bootstrapped_at": "2026-07-07T19:27:32Z",
"origin": "bootstrap",
"repair_count": 0,

"dockerfile_path": "Dockerfile",
"port": 8000,

"validation_status_codes": [200, 301, 302, 404],

"input_checksums": {
"dockerfile": "79ae4e91373124fd",
"dependency_manifest": "a4af380b1d34c482"
},

"system_packages": [],
"known_fixes": [
"web/package.json's postbuild script (`cp -r build/ ../server/static/build/`) fails if server/static doesn't exist yet — added `RUN mkdir -p /app/server/static` before `yarn build` in the frontend build stage.",
"Container runs at runtime as uid/gid 65534 (nobody), but files land as root during the Docker build — SQLite couldn't create db.sqlite3 in /app/server. Added `RUN chown -R 65534:65534 /app/server` in the runtime stage."
],
"discovery_notes": "Two-app monorepo: a Django 1.11 backend at server/ (SQLite, no external services, Pipfile/Pipfile.lock via pipenv) and a Create React App frontend at web/ (react-scripts 1.0.10, yarn.lock) with no API calls of its own. The repo's own build wiring (web's postbuild script) copies the React production build into server/static/build, and Django serves that directory plus a catch-all index view — so the single canonical app server is the Django backend serving the built frontend, started via a two-stage Dockerfile (node:10 builder + python:3.6-slim runtime). No vault secrets or sidecars needed: DEBUG=True, hardcoded dev SECRET_KEY, SQLite embedded, ALLOWED_HOSTS=[] which Django's dev special-case already permits for the default 'localhost' Host header forward_app_request sends."
}
16 changes: 16 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.git
.github

# Python
__pycache__
*.pyc
*.egg-info
.venv
server/db.sqlite3

# Node
node_modules
web/build
server/static/build
.cache
*.log
28 changes: 28 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by CodePress bootstrap-app-server.
# Edits are preserved, but running /codepress-bootstrap-app-server again may overwrite them.

# ---- Stage 1: build the React frontend, then let its postbuild script copy
# the production build into server/static/build (this repo's Django app
# serves that directory directly, see server/server/server/settings.py) ----
FROM node:10 AS web-builder
WORKDIR /app
COPY server/ ./server/
COPY web/ ./web/
WORKDIR /app/web
RUN yarn install --frozen-lockfile
RUN mkdir -p /app/server/static
RUN yarn build

# ---- Stage 2: Django app server ----
FROM python:3.6-slim AS runtime
WORKDIR /app/server
COPY server/Pipfile server/Pipfile.lock ./
RUN pip install --no-cache-dir pipenv \
&& pipenv install --system --deploy --ignore-pipfile
COPY server/ ./
COPY --from=web-builder /app/server/static ./static
# The runtime sandbox executes the container as uid/gid 65534 (nobody), which
# needs write access to this directory so Django/SQLite can create db.sqlite3.
RUN chown -R 65534:65534 /app/server
EXPOSE 8000
CMD ["sh", "-c", "python manage.py migrate --noinput && python manage.py runserver 0.0.0.0:8000"]