Make bin/setup fail fast, fail clearly, and stop dropping the dev DB - #7172
Draft
compwron wants to merge 2 commits into
Draft
Make bin/setup fail fast, fail clearly, and stop dropping the dev DB#7172compwron wants to merge 2 commits into
compwron wants to merge 2 commits into
Conversation
bin/setup had two control-flow bugs, destroyed local data on every run, and
surfaced its most common failure several minutes in.
Bugs:
- `system!('bundle check') || system!('bundle install')` could never fall
through. `system!` passes `exception: true`, so a failing `bundle check`
raised instead of reaching the fallback -- meaning every fresh clone, the
one case the line exists for, crashed instead of installing gems. Use the
non-raising `try_system` for commands whose failure is a branch rather than
an error (bin/update already had this right). Same dead `|| abort` after
`npm ci`.
- The Ruby version mismatch path called bare `exit`, which exits 0. Callers
such as .devcontainer/post-create.sh read that as success, so a Codespace
came up with nothing installed and no error. Now aborts non-zero.
Data loss:
- `db:reset` ran unconditionally, dropping the development database, while the
file's own comment claimed idempotency. Default to `db:prepare`; keep the
destructive path behind an explicit `--reset` (or FORCE_DB_RESET=1), which
is what the devcontainer now passes.
Fail fast:
- Move all cheap validation ahead of the first `gem install`: .env creation,
a warning for keys in .env.example missing from .env, an exact Ruby version
comparison, a Node presence and major-version check against engines.node,
and a real postgres connection attempt using the .env values. Errors name
the variable to edit instead of printing a warning emoji and pressing on.
The checks are stdlib-only since they run before `bundle install`.
Also drops the redundant `gem install foreman` (bin/dev installs it on
demand) and the no-op `bin/rails restart` on a fresh clone, adds
`--skip-assets` and `--help`, unbuffers stdout so piped logs stay ordered,
and ends with the seed login and next steps.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
bin/setup currently reads DB connection settings only from .env (not existing process ENV), which will break devcontainer setups that supply DATABASE_HOST/POSTGRES_* via containerEnv.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR improves the first-run developer experience by making bin/setup validate prerequisites early, handle expected failures without raising, and avoid destructive database resets unless explicitly requested.
Changes:
- Reworks
bin/setupto add preflight checks (Ruby/Node/Postgres), clearer abort messages, and non-destructivedb:prepareby default with an explicit--reset. - Updates docs to reflect the new setup behavior and flags (
--reset,--skip-assets,--help). - Updates devcontainer post-create to use
bin/setup --resetfor clean container provisioning.
File summaries
| File | Description |
|---|---|
| README.md | Updates troubleshooting guidance and documents new bin/setup flags. |
| CLAUDE.md | Updates the one-time setup command description to mention re-run safety and flags. |
| bin/setup | Adds preflight validation, clearer control flow around dependency install, and safer DB handling with --reset. |
| .devcontainer/post-create.sh | Runs bin/setup --reset in fresh containers. |
Review details
- Files reviewed: 3/4 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| 3. Run `bin/setup` | ||
| `bin/setup` checks the database before doing anything slow, and prints the host, port | ||
| and user it tried. Either postgres is not running, or the credentials in `.env` are | ||
| wrong — update `POSTGRES_USER`, `POSTGRES_PASSWORD` and `DATABASE_HOST` to match your |
The repo has 24 shell scripts and no shell linter. Adds a Shell lint workflow running shellcheck, and fixes the four files that kept it from being green. The workflow selects scripts by shebang rather than by path: most of them have no extension (bin/dev, docker/*), and bin/ also holds Ruby scripts, so neither a glob nor a directory list gets the set right. Gated at --severity=warning. The tree still has ~35 info/style findings, mostly SC2086 (unquoted variables) across bin/git_hooks and docker/. Those are worth fixing, but gating on them now would mean rewriting scripts unrelated to whatever PR is under review -- the same gradual-adoption stance as .standard_todo.yml. The comment in the workflow says to lower the threshold as they get cleaned up. Findings fixed: - bin/git_hooks/lint: `git diff --name-only HEAD~$(git cherry ... | wc -l)` was unquoted (SC2046), and on BSD/macOS `wc -l` pads its output with spaces -- so word splitting passed git `HEAD~` and `1` as separate arguments and the --unpushed path died with "ambiguous argument '1'". Strip the padding and quote. Also `cd ... || exit 1` in five places (SC2164), so a failed cd can no longer run a linter in the wrong directory. - docker/run: `docker compose run web $@` -> `"$@"` (SC2068). Unquoted `$@` re-splits arguments, so `docker/run rspec -e "some example"` lost its quoting. - bin/git_hooks/update-dependencies: replace the two-branch `[ $(git diff ... | wc -l) -gt 0 ]` test with a single `git diff --quiet` over both pathspecs (SC2046, SC1083). Verified equivalent for lockfile-only, package.json-only and unrelated changes. - .devcontainer/post-create.sh: add a shebang (SC2148). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
bin/setupis the first thing a new contributor runs. Today it has two control-flow bugs, destroys local data on every run, and surfaces its single most common failure several minutes in.Bugs fixed
1.
bundle check || bundle installcould never fall through.exception: truemeans a failingbundle checkraises; the||is dead code. Verified:So the one situation the line exists for — gems not installed yet, i.e. every fresh clone — crashed the script instead of installing them.
bin/update:20already had the correct non-raising form. This PR adds an explicittry_systemfor commands whose non-zero exit is a branch rather than an error, and uses it here and fornpm ci(whose|| abortwas unreachable too, and whose message blamed a missing npm for what is almost always a lockfile mismatch).2. The Ruby version mismatch path exited 0.
Bare
exitreports success, so.devcontainer/post-create.shand any CI caller treated "wrong Ruby, nothing installed" as a clean setup. Now aborts non-zero, with the install command for rbenv/rvm/asdf.Stops dropping the development database
bin/rails db:resetran unconditionally — directly under a comment claiming the script "is idempotent, so that you can run it at any time". Re-running setup silently destroyed local data.db:prepare: creates and seeds when needed, preserves what is there.--reset(orFORCE_DB_RESET=1) keeps the destructive path, explicitly..devcontainer/post-create.shpasses it, since a fresh container has nothing to lose.Fails fast instead of late
Postgres reachability used to be a printed warning (
⚠️ If you use docker to run postgres, make sure your database is running), and Node was never checked at all — so a wrong Node blew up onnpm ciat the very end, after the full DB seed. All cheap validation now runs before the firstgem install:.envcreated from.env.example, plus a warning listing keys that.env.exampledefines and.envlacks (previously these silently drifted).current_ruby_version.include?(expected), soruby 4.0.61would have passed.engines.node..envvalues, viapsql(validates the user too) falling back topg_isready, or skipped with a note if neither is installed.Errors name the variable to change:
This replaces the README's "bin/setup fails with a credentials error" troubleshooting entry.
Smaller things
gem install foreman—bin/devalready installs foreman on demand.bin/rails restart, which restarts nothing on a fresh clone.--skip-assetsand--help.$stdout.sync = true, so progress and error output stay in order when piped to a log rather than a terminal (the Codespaces post-create case).bin/dev, the URL, and the seed login — at the moment you need them.Verification
Preflight paths exercised individually against a sandboxed app root, all with the real code from
bin/setup:exit=0exit=1, prints rbenv/rvm/asdf commandsexit=1, names host/port/userPOSTGRES_USERexit=1.envexit=1, points atnvm installexit=1Full
bin/setuprun end to end in a clean worktree with no.env(so it exercised first-time setup): green,exit=0, throughdb:prepare,db:test:prepare,after_party:run,npm ciand both asset builds.standardrb bin/setupis clean.One caveat on the wrong-password case: my local postgres uses
trustauth, so a bad password connects anyway and the check passes. The check does catch a bad user, a bad host and a bad port. Credential validation is therefore only meaningful where postgres actually requires a password — worth knowing, though it is strictly better than the previous warning-only behavior.A second back-to-back run failed on
db:test:preparewithPG::ObjectInUse— a stalerspecprocess on my machine was holdingcasa_test. Pre-existing and unrelated; the original script fails identically there.Docs updated
README.md(troubleshooting entry replaced, flags table added),.devcontainer/post-create.sh(--reset),CLAUDE.mdcommand table.Not in this PR
bin/setupandbin/updateremain near-duplicates with their own divergentsystem!semantics. Extracting a shared helper would stop them drifting again, but it touches a second entrypoint, so I left it out to keep this diff reviewable.🤖 Generated with Claude Code
Added: shellcheck CI job
The repo has 24 shell scripts and no shell linter, so a new
Shell lintworkflow runs shellcheck over them.Scripts are selected by shebang, not by path — most have no extension (
bin/dev,docker/*), andbin/also holds Ruby scripts, so neither a glob nor a directory list gets the set right. Verified on the runner: 24 scripts found, job green in 10s, and no Ruby script picked up.Gated at
--severity=warning. The tree still has ~35 info/style findings, mostly SC2086 (unquoted variables) acrossbin/git_hooks/anddocker/. Those are worth fixing, but gating on them now would mean rewriting scripts unrelated to whatever PR is under review — the same gradual-adoption stance as.standard_todo.yml. A comment in the workflow says to lower the threshold as they get cleaned up.Findings fixed to make it green
Two of these were live bugs, not style nits:
bin/git_hooks/lint— the--unpushedpath was broken on macOS.git diff --name-only HEAD~$(git cherry -v main ${current_branch} | wc -l) HEADwc -lpads its output with spaces on BSD/macOS, and the substitution was unquoted, so word splitting handed gitHEAD~and1as two arguments:Now strips the padding into a variable and quotes it; the rewritten form returns the expected file list. (Linux
wc -ldoesn't pad, which is why this survived.) Also adds|| exit 1to five barecds (SC2164), so a failedcdcan no longer run a linter against the wrong directory.docker/run— unquoted$@re-split arguments (SC2068), sodocker/run rspec -e "some example"lost its quoting. Now"$@".bin/git_hooks/update-dependencies(SC2046, SC1083) — replaced the two-branch[ $(git diff ... | wc -l) -gt 0 ]test with a singlegit diff --quietover both pathspecs. Verified equivalent against a purpose-built repo across lockfile-only,package.json-only, and unrelated changes:.devcontainer/post-create.sh— added a shebang (SC2148); it had none.Gate verification
Beyond the green run, I confirmed the job actually fails when it should: adding a new extensionless script with an SC2164 defect was discovered and failed the step (exit 1); fixing the defect returned it to exit 0.
yamllinton the new workflow produces the same two warnings (truthyonon:, comment spacing on the pinned action SHA) as every existing workflow, and no errors.Unrelated CI failure
ruby_lintis red on this PR, but it is red onmaintoo — the last twomainruns failed withStyle/RedundantStructKeywordInitinapp/services/supervisor_dashboard.rbandapp/services/volunteer_dashboard.rb, from a standard/rubocop version bump. Neither file is touched here. Worth its own one-line PR.