Run rubygems.org
A Rails 8 app (internal name: gemcutter) that needs Postgres, OpenSearch, and Memcached reachable on 127.0.0.1:5432/9200/11211. smoke.sh probes those ports and works whether the backing services are running in Docker, via native installs, or are already running on the host; everything else (Ruby 4, Puma, headless Chrome) runs on the host.
The agent path is ./.agents/skills/run-rubygems-org/smoke.sh — it brings services up, boots Rails on :3000 if it isn't already, curls four key endpoints, and, when a Chromium binary is available, writes three PNG screenshots to tmp/run-skill/.
All paths in this doc are relative to the repo root.
Canonical location:
.agents/skills/run-rubygems-org/— the cross-tool skill convention picked up by Codex CLI, OpenCode, and Gemini CLI. Claude Code reads it through the symlink at.claude/skills/run-rubygems-org/.If
smoke.shfails, see "When this skill fails" below before improvising —LEARNINGS.mdlikely has your fix.
Prerequisites (one-time)
You need Ruby 4 (via mise/asdf from .ruby-version), and Postgres + OpenSearch + Memcached reachable on 127.0.0.1:5432/9200/11211. smoke.sh doesn't care how they're running — Docker, brew services, apt, or already-on-the-host — it just probes the ports.
If you're on a fresh machine, pick whichever path you prefer:
# Path A: Docker (one command for all three backing services)
mise install # ruby 4.0.5 per .ruby-version
docker compose up -d db cache search
bin/setup # bundle, db:prepare, db:seed, playwright install
# Path B: Native (macOS via Homebrew)
mise install
brew install postgresql@14 memcached
brew services start postgresql@14
brew services start memcached
# OpenSearch isn't in Homebrew; run it via docker even in a "native" setup:
docker run -d -p 9200:9200 \
-e discovery.type=single-node -e DISABLE_SECURITY_PLUGIN=true \
-e 'OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m' \
opensearchproject/opensearch:2.13.0
bin/setup
See CONTRIBUTING.md > Development Setup for the Linux/apt variant. bin/setup runs bin/playwright install --with-deps chromium; smoke.sh shells out to bin/playwright screenshot, which uses that same bundled Chromium — no separate browser needed.
Two traps on fresh machines:
mise installalone doesn't put Ruby on PATH in non-interactive shells. Activate it first (eval "$(mise activate bash --shims)") or prefix commands withmise exec --; otherwisebin/setupruns against the system Ruby and bundler aborts with a version mismatch.- Native Postgres on Linux needs
trustauth.config/database.ymlexpects different passwords for the samepostgresrole in dev (devpassword) and test (testpassword), which only works when the server doesn't check passwords. Docker handles this viaPOSTGRES_HOST_AUTH_METHOD=trustand Homebrew trusts local connections by default, but apt's Postgres defaults toscram-sha-256— set the local/127.0.0.1entries inpg_hba.conftotrustand restart, orbin/rails db:preparefails withpassword authentication failed for user "postgres".
Run (agent path — preferred)
./.agents/skills/run-rubygems-org/smoke.sh
That single command:
- Probes Postgres (
:5432), Memcached (:11211), and OpenSearch (:9200). If a service is already listening (native brew/apt install, or some other running container), it's left alone. If something is down anddocker compose+docker-compose.ymlare both available, it falls back todocker compose up -d <service>. If neither path works, it fails with the exact install commands to run. - If nothing is on :3000, launches
bundle exec rails server -p 3000 -b 127.0.0.1undernohup(pid →tmp/run-skill/rails.pid, log →tmp/run-skill/rails.log). Otherwise reuses the existing server. - Polls
GET /until it returns 200 (timeout 60s — cold start is ~3s here, ~10–15s on first boot after a code change due to Tailwind compile). - Curls four endpoints and fails loudly on any non-200 or missing-substring:
/(homepage HTML)/api/v1/gems/rubygem0.json(gem JSON API)/versions(compact_index — whatbundleandgemfetch)/gems/rubygem0(gem detail page)
- Screenshots three pages with headless Chrome at 1280px wide →
tmp/run-skill/{home,gem,signin}.png.
rubygem0 exists because db:seed creates it; if you've wiped the DB, re-run bin/rails db:seed.
To stop a server smoke.sh started:
kill "$(cat tmp/run-skill/rails.pid)"
Run (human path)
bin/rails s # foreground on :3000
Opens nothing — just listens. Useless headless; for an agent driving the app, always use smoke.sh so you get the screenshots and HTTP assertions.
Direct invocation (poking internal code)
Most PRs in this repo touch a model, job, policy, or controller — not the HTML surface. For those, skip smoke.sh and call the code directly. All of these run against the dev DB and respect any local changes without restarting a server:
# One-liner: import + call + observe (matches AGENTS.md examples)
bin/rails runner 'p Rubygem.find_by(name: "rubygem0")&.versions&.count'
# REPL — for exploratory work or anything multi-step
bin/rails c
# Run one test file (fastest feedback loop for a model/job change)
bin/rails test test/models/rubygem_test.rb
bin/rails test test/models/rubygem_test.rb:42 # one line
bin/rails test -n /pattern/ # name pattern
# Enqueue / run a job inline against the dev DB
bin/rails runner 'YourJob.new.perform(Rubygem.last.id)'
If a job/policy depends on the gem index or OpenSearch being current, run the indexer first:
bundle exec rake gemcutter:index:update # compact_index / specs.*.gz
bundle exec rake searchkick:reindex CLASS=Rubygem # OpenSearch
When this skill fails (learn-and-teach loop)
This skill keeps a running LEARNINGS.md so each failure becomes a one-time cost, not a recurring one. When smoke.sh fails with something not already in the Troubleshooting table:
- Read
LEARNINGS.mdfirst — Ctrl-F your error string; someone may have already solved it. - Read
tmp/run-skill/diagnostic.txt—smoke.shwrites a structured environment snapshot (ports, docker state, rails log tail, ruby/bundle versions) every time it fails. - Fix it. Capture the exact command(s) that worked.
- Add an entry to
LEARNINGS.mdusing the template at the top of that file. Don't skip this even if the fix is "obvious" to you — it won't be obvious to the next agent in a fresh container at 3am. - Graduate stable fixes. If the failure mode is likely to recur (not a one-off environment quirk):
- Add a row to the Troubleshooting table below (symptom → fix), and/or
- Update
smoke.shto auto-detect or auto-recover (e.g. add a probe, extend the docker fallback) - Then mark the LEARNINGS.md entry Status: graduated with a link to your commit/PR.
This is the skill teaching itself. LEARNINGS.md is the why behind every SKILL.md / smoke.sh change — keep it even after graduation.
Tests
bin/rails test # unit/integration
bin/rails test test/models/rubygem_test.rb:42 # single test
DB_HOST=db bin/rails test # ONLY when Postgres isn't on localhost (e.g. dev containers) — see Gotchas
bin/rails test:system # Playwright + Chrome
bin/ci # full CI suite (lint + brakeman + tests)
Gotchas
.env.localis excluded in the test environment. dotenv loads.env.localfor development but not for test, so aDB_HOSTset there is invisible to tests.config/database.ymldefaultsDB_HOSTtolocalhost(TCP) for both dev and test, so a standard local setup needs noDB_HOSTat all — but when Postgres lives on another host (e.g.dbin a dev container), pass it inline:DB_HOST=db bin/rails test.- There is no
/upendpoint. Rails 8's default health check is not wired up; useGET /(homepage) as the readiness probe./upreturns 404. - First boot prints ~50 lines of Datadog APM/CI-Visibility noise before "Listening on …". This is normal —
dd-trace-rbautoloads in development. Don't mistake it for failure. - OpenSearch may report
status: yellow. That's expected for a single-node cluster; the smoke script only requires_cluster/healthto respond, not be green. - Seeded gems are
rubygem0/rubygem1/rubygem2, not real gems./api/v1/gems/rails.jsonreturns "This rubygem could not be found." — userubygem0in any smoke check. - Tailwind runs in a separate process (
bin/rails tailwindcss:watchis auto-spawned in dev). It writes CSS asynchronously after boot; if the homepage looks unstyled in a screenshot, give it 2–3 more seconds. bin/setupis destructive of dev DB schema. It runsdb:preparewhich will create+migrate. To load real anonymized data, usescript/load-pg-dump(see AGENTS.md).- Screenshots go through
bin/playwright, not a hand-found Chromium.bin/playwrightis the Node CLI pinned to theplaywright-ruby-clientgem version, andplaywright screenshotresolves its own bundled browser — so we don't care where Chromium lives on disk. If you blow away the cache,bin/playwright install --with-deps chromiumrehydrates it.
Troubleshooting
| Symptom | Fix |
|---|---|
could not connect to server: Connection refused (psql/Rails) |
Postgres isn't running. Docker: docker compose up -d db. Native (macOS): brew services start postgresql@14. |
Faraday::ConnectionFailed against localhost:9200 |
OpenSearch is down or still booting. smoke.sh will auto-start it via docker if available — otherwise see the OpenSearch docker run line in Prerequisites. Wait ~5s for /_cluster/health to respond. |
smoke.sh fails: <svc> is not responding ... docker compose isn't available here |
The probe found nothing on the port AND there's no docker on PATH. The error message lists the exact native-install command for that service — run it, then re-run smoke.sh. |
Address already in use - bind(2) for "127.0.0.1" port 3000 |
A previous Rails is still alive: pkill -f 'puma.*3000' (or kill $(cat tmp/run-skill/rails.pid) if smoke.sh started it). |
Smoke says homepage never returned 200 (last 500) |
tail -100 tmp/run-skill/rails.log — usually a missing migration (bin/rails db:migrate) or DB seed (bin/rails db:seed). |
Smoke says gem JSON API failed (expected 200, got 404) |
DB is fresh and unseeded: bin/rails db:seed recreates rubygem0. |
screenshot <name> failed in smoke output |
Chromium isn't installed for the pinned playwright version: bin/playwright install --with-deps chromium. |
bin/playwright unavailable in smoke output |
No node on PATH or bin/playwright missing — install Node (or use mise/asdf), then re-run. |
| Screenshot is blank/white | Server returned 200 but the page errored client-side; open tmp/run-skill/rails.log and look for the request just before — Tailwind not ready yet is the most common cause. Re-run smoke.sh. |
FATAL: password authentication failed for user "postgres" during bin/setup / db:prepare |
Native Postgres with password auth (the apt default). Dev and test expect different passwords for the same postgres role, so the server must not check them: set the local/127.0.0.1 entries in pg_hba.conf to trust, restart Postgres, re-run. See Prerequisites. |
Your Ruby version is 3.x, but your Gemfile specified 4.0.x right after mise install |
mise isn't activated in this (non-interactive) shell: eval "$(mise activate bash --shims)", or prefix commands with mise exec --, then re-run. |
docker pull fails: You have reached your unauthenticated pull rate limit |
Docker Hub rate limit — common from fresh containers/CI egress IPs. OpenSearch has a drop-in mirror: swap the image in the Prerequisites docker run line for public.ecr.aws/opensearchproject/opensearch:2.13.0. Postgres and memcached can come from apt/brew instead (see Prerequisites traps for the Postgres auth caveat). |
Source: rubygems/rubygems.org — distributed by TomeVault.