diff --git a/.webmux.yaml b/.webmux.yaml index 27c80f7e37..507fe63580 100644 --- a/.webmux.yaml +++ b/.webmux.yaml @@ -5,9 +5,19 @@ workspace: mainBranch: main worktreeRoot: ../windmill__worktrees defaultAgent: claude + # A new worktree is branched from the *local* `main` ref, so a stale local main means every + # new worktree starts behind. This keeps it current: fetch origin/main + fast-forward merge. + # Fast-forward only — it no-ops rather than forcing if local main has diverged. + autoPull: + enabled: true + intervalSeconds: 300 startupEnvs: CARGO_FEATURES: "quickjs" + # true clones the base `windmill` DB via CREATE DATABASE ... TEMPLATE, which first + # terminates every open connection to `windmill` — expect the main dev instance to drop. + # false creates an empty DB and runs migrations. Either way the license key is copied over + # and pre-remove drops the DB. See scripts/worktree-common.sh. WM_CLONE_DB: false USE_RUST_PLUGIN: false diff --git a/AGENTS.md b/AGENTS.md index 9325430e24..a243e7cff9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,9 +50,15 @@ Open-source platform for internal tools, workflows, API integrations, background > defaults in this section apply only to a plain single checkout. **Discover the real > values before running anything** — see "Per-worktree ports and database" below. +**Check whether they are already running before starting anything.** In a webmux worktree +(`$WEBMUX_WORKTREE_PATH` is set) the backend and frontend are already up in sibling tmux panes — +use those, don't spawn your own. `tmux list-panes -t "$(tmux display-message -p -t "$TMUX_PANE" +'#{window_id}')" -F '#{pane_index} #{pane_current_command}'` shows what is running; read its log +with `tmux capture-pane`, and see `backend/CLAUDE.md` to restart it with different cargo features. +A second server started in your own shell fights the first one for the port. The commands below +are for a plain checkout with nothing running. + - **Backend**: `cargo run` from `backend/` (API at http://localhost:8000) -- **DuckDB local jobs**: before running DuckDB scripts locally, build the FFI shared library with `cd backend/windmill-duckdb-ffi-internal && ./build_dev.sh`. Re-run it after clean builds or when `backend/target/debug/libwindmill_duckdb_ffi_internal.*` is missing. The bundled DuckDB compile (~2min) is cached in a per-user dir shared across worktrees, so a fresh worktree reuses it and the build is near-instant. -- **Data pipelines (DuckLake) from source**: a plain `cargo run` (even `--features quickjs`) advertises a `duckdb` worker tag but **cannot** execute DuckDB scripts and has **no** working S3 proxy (DuckLake writes 404). Build CE DuckLake with `cargo run --features quickjs,duckdb,parquet,private` (add `,python` for Python scripts, `,enterprise,license` for EE) **and** build the FFI (bullet above). See `backend/CLAUDE.md` → "Running data pipelines (DuckLake) from source" for the exact feature sets and the two feature-gate gotchas. - **Frontend**: `REMOTE=http://localhost:8000 npm run dev` from `frontend/` (port 3000+) - **DB**: `psql postgres://postgres:changeme@localhost:5432/windmill` - **Login**: `admin@windmill.dev` / `changeme` @@ -61,10 +67,20 @@ Open-source platform for internal tools, workflows, API integrations, background ### Per-worktree ports and database -A worktree's `.env` / `.env.local` (repo root) and `backend/.env` hold its own -`DATABASE_URL` and `PORT`; the database is typically `windmill_` -(branch `dbt-runtime` → `windmill_dbt_runtime`). Read them, or discover from what is -already running: +In a webmux worktree the authoritative values live in +`$(git rev-parse --git-dir)/webmux/runtime.env` — `BACKEND_PORT`, `FRONTEND_PORT`, +`DATABASE_URL`, `CARGO_FEATURES`, `WM_DB_NAME`. Every pane sources it at startup. Read that +first: it is not a `.env*` file, so the repo's secret-file read rules don't stand in the way. + +In a plain checkout, fall back to `.env` / `.env.local` (repo root) and `backend/.env`. + +The database is named after the **worktree directory, not the branch** (`scripts/worktree-common.sh`): +`windmill_` + the directory basename with `-` → `_`, which Postgres then truncates at 63 +characters. Branch `hugo/win-2340-ai-agent-evals-standalone-agent-runs-and-eval-datasets` sits in +a worktree directory named `win-2340-…`, so its database is +`windmill_win_2340_ai_agent_evals_standalone_agent_runs_and_eval` — no `hugo_`, and the tail +chopped. Take `WM_DB_NAME` from `runtime.env` instead of reconstructing the name. Read those, or +discover from what is already running: ```bash psql postgres://postgres:changeme@localhost:5432/postgres -tAc \ diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md index 468fc40b67..98ad6076d5 100644 --- a/backend/CLAUDE.md +++ b/backend/CLAUDE.md @@ -32,23 +32,69 @@ MUST **restart the backend with the appropriate features** for what you're worki ### Restarting the dev backend with the right features -The backend runs in tmux pane 1 as `cargo watch -x "run --features <…>"`. To restart it with a -different feature set — scope kills by pid/cwd, **never** `pkill -f target/debug/windmill` (it -kills every sibling worktree's backend): +Restart in the **same pane**, so the relaunch inherits that pane's `DATABASE_URL`, `BACKEND_PORT` +and the rest of `runtime.env`. Scope every kill to this worktree — **never** +`pkill -f target/debug/windmill`, which kills every sibling worktree's backend. + +1. Find the backend pane by what it is running, not by index. The index depends on the webmux + profile: pane 1 is the backend under `full`, but the *frontend* under `frontendOnly`. -1. Stop the current run: `tmux send-keys -t C-c`, then kill *this worktree's* - `cargo-watch` pid (find it via `/proc//cwd`). -2. Relaunch in the same pane so it inherits the shell's `DATABASE_URL` etc.; the pane env's - `PORT` may be stale, so set it explicitly: ```bash - export PORT=$BACKEND_PORT - cargo watch -x "run --features enterprise,private,parquet,quickjs" + WIN=$(tmux display-message -p -t "$TMUX_PANE" '#{window_id}') + tmux list-panes -t "$WIN" -F '#{pane_index} #{pane_current_command} #{pane_pid}' ``` -3. Wait for `health check completed` in the pane before hitting the API. + +2. Read the feature set it is **actually** running. `CARGO_FEATURES` in `runtime.env` is only what + the pane started with, and goes stale the first time anyone restarts by hand: + + ```bash + ps --ppid -o args= + # /home/hugo/.cargo/bin/cargo-watch watch -x run --features quickjs + ``` + +3. Stop it and relaunch with the extended set. `PORT` in the pane shell can be stale, so pass it + explicitly: + + ```bash + tmux send-keys -t "$WIN." C-c + tmux send-keys -t "$WIN." 'PORT=$BACKEND_PORT cargo watch -x "run --features quickjs,private,parquet"' Enter + ``` + + Carry over every feature the old command had unless you mean to drop one — rebuilding the list + from memory is how a backend silently loses `quickjs`. + +4. Persist the new set so a recreated pane starts with it: set `CARGO_FEATURES` in + `$(git rev-parse --git-dir)/webmux/runtime.env`. That file is read at pane startup only, so it + changes nothing about the process you just relaunched — step 3 is what takes effect now. + +5. Re-capture the pane until `health check completed` appears before hitting the API. A cold + rebuild takes ~60s, and the previous run's success line is still in the scrollback, so a + capture taken too early reads as ready when it isn't. cargo-watch only re-runs on a file change, so after an idle/failed run `touch README.md` (from `backend/`, where the watch runs) is a cheap retrigger (touching a `.rs` forces a full rebuild). +### An orphaned backend is holding the port + +If the pane's `cargo watch` looks alive but the API never answers, or the build ends in an +address-already-in-use error, a backend from an earlier run is probably still bound to the port. +It gets reparented to `systemd --user` when its shell dies, so it survives everything that looks +like a cleanup. + +Confirm all three before killing anything — a dozen sibling worktrees run their own backend, and +`pkill -f windmill` (or `-f target/debug/windmill`) kills every one of them: + +```bash +ss -ltnp | grep ":$BACKEND_PORT" # 1. which pid holds the port +readlink /proc//cwd # 2. must be THIS worktree's backend/ +ps -o ppid= -p # 3. parent is systemd/pid 1, not your pane's cargo-watch +``` + +Only when the port owner is this worktree's backend **and** it is orphaned, kill that single pid +(`kill `, then `kill -9` if it does not exit). Ask first when there is a human in the loop; +unattended, the three checks are what make it safe. Then `touch README.md` to retrigger the +watch. + ### What each feature gate does (the ones you'll actually toggle) `backend/Cargo.toml` `[features]` is the source of truth; this is the practical dev map. Combine diff --git a/scripts/worktree-common.sh b/scripts/worktree-common.sh index 65c396d458..672b9f9f65 100755 --- a/scripts/worktree-common.sh +++ b/scripts/worktree-common.sh @@ -164,10 +164,20 @@ wm_find_ee_repo() { return 1 } +# The EE commit this CE checkout builds against. CI reads the same file, so basing a new EE +# worktree on it keeps a local `cargo check --features private` on the tree CI compiles. Local +# `main` in the EE repo is not a substitute: nothing fast-forwards it, so it drifts behind the pin. +wm_ee_pinned_ref() { + local repo_root=$1 ref + ref="$(tr -d '[:space:]' < "${repo_root}/backend/ee-repo-ref.txt" 2>/dev/null)" || return 1 + [[ -n "$ref" ]] || return 1 + printf '%s' "$ref" +} + wm_setup_ee_worktree() { local repo_root=$1 local main_repo_root=$2 - local ee_repo branch wt_basename ee_worktree_dir ee_rel rust_plugin + local ee_repo branch wt_basename ee_worktree_dir ee_rel rust_plugin ee_ref if ! ee_repo="$(wm_find_ee_repo "$repo_root" "$main_repo_root")"; then return @@ -189,8 +199,11 @@ wm_setup_ee_worktree() { elif git -C "$ee_repo" show-ref --verify --quiet "refs/remotes/origin/$branch" \ && git -C "$ee_repo" worktree add --track -b "$branch" "$ee_worktree_dir" "origin/$branch" 2>/dev/null; then echo "Created EE worktree at $ee_worktree_dir (tracking origin/$branch)" + elif ee_ref="$(wm_ee_pinned_ref "$repo_root")" \ + && git -C "$ee_repo" worktree add -b "$branch" "$ee_worktree_dir" "$ee_ref" 2>/dev/null; then + echo "Created EE worktree at $ee_worktree_dir (new branch: $branch from pinned ${ee_ref:0:12})" elif git -C "$ee_repo" worktree add -b "$branch" "$ee_worktree_dir" main 2>/dev/null; then - echo "Created EE worktree at $ee_worktree_dir (new branch: $branch from main)" + echo "Created EE worktree at $ee_worktree_dir (new branch: $branch from main — pin unavailable)" else echo "Warning: Could not create EE worktree for branch $branch" fi