feat: project run skill for dev and isolated lab launches#36
Conversation
Encodes the launch quirks agents kept relearning: Wayland leak (GDK_BACKEND=x11 + unset WAYLAND_DISPLAY), labtest single-instance identifier, private D-Bus for the tray, Xvfb+xfwm4, real-user-data warning (XDG dirs not keyed on identifier), inspect-then-kill pid cleanup. Canonical in .agents/skills/run with a .claude/skills symlink. Refs #35
|
@codex review |
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bd5302e49e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for candidate in {1421..1439}; do ! ss -ltnH "sport = :$candidate" | grep -q . && { PORT=$candidate; break; }; done | ||
| : "${PORT:?No free port in 1421-1439}" | ||
|
|
||
| bun run dev -- --port "$PORT" --strictPort >"/tmp/pickscribe-vite-$PORT.log" 2>&1 & |
There was a problem hiding this comment.
Install dependencies before the lab launch
In a fresh checkout or agent container where setup scripts have not run, the isolated lab workflow can be the first path used for screenshots; because this section starts with bun run dev and never runs bun install --frozen-lockfile, the local vite/tauri binaries are missing and the launch fails before the lab starts. The normal-dev section includes the install, but the lab workflow is presented independently, so agents following it cannot launch the isolated instance from a clean repo.
Useful? React with 👍 / 👎.
| Pick an unused display and frontend port, then start Vite separately: | ||
|
|
||
| ```bash | ||
| DISPLAY_NUM=97 |
There was a problem hiding this comment.
Pick an actually unused X display
When another lab run or X server is already using :97, this fixed assignment lets Xvfb fail while the later DISPLAY=:$DISPLAY_NUM app and screenshot commands still target that existing display (or fail with no clear cause). The section says to pick an unused display, so the snippet should scan/reserve a free display instead of hard-coding one that concurrent agents can collide on.
Useful? React with 👍 / 👎.
|
|
||
| ```bash | ||
| DISPLAY_NUM=97 | ||
| for candidate in {1421..1439}; do ! ss -ltnH "sport = :$candidate" | grep -q . && { PORT=$candidate; break; }; done |
There was a problem hiding this comment.
Fail when the port scanner is unavailable
In minimal agent images where ss/iproute2 is not installed, this negated pipeline treats the ss: command not found case as “no listener” because grep sees no input, so it blindly selects 1421 instead of failing or using another probe. If that port is already occupied, the subsequent bun run dev -- --port "$PORT" --strictPort exits and the lab never starts, even though the workflow claims it scanned for a free port.
Useful? React with 👍 / 👎.
Lab launches now override HOME + XDG dirs to a fresh temp profile (CARGO_HOME/RUSTUP_HOME kept real so tauri dev still compiles), matching the sibling apps after their reviews proved the labtest identifier alone leaks real user data. Verified: lab created its own pickscribe.db and WebKit storage under the temp home, real config/data mtimes unchanged. Whisper models are an explicit opt-in symlink. Free X display scanned instead of hard-coded; lab home removed in cleanup.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b2b2587c29
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| GDK otherwise prefers Wayland: `DISPLAY=:N` alone can open on the live desktop. The app has a tray icon, so give it a private D-Bus session. `REAL_HOME` preserves the existing Cargo and Rustup installations: | ||
| ```bash | ||
| dbus-run-session -- env -u WAYLAND_DISPLAY GDK_BACKEND=x11 DISPLAY=":$DISPLAY_NUM" \ | ||
| HOME="$LAB_HOME" XDG_CONFIG_HOME="$LAB_HOME/.config" XDG_DATA_HOME="$LAB_HOME/.local/share" XDG_CACHE_HOME="$LAB_HOME/.cache" \ |
There was a problem hiding this comment.
Disable real-session side effects in the lab profile
When this command points HOME/XDG at an empty profile, PickScribe falls back to its defaults (paste.method = auto, copy_to_clipboard = true, and local_only = false in src/config.rs) while still inheriting the caller's process environment. In the documented dictation-test path on a Wayland developer machine with the recommended ydotool service and/or cloud API keys present, stopping dictation can paste through global input automation into the user's real focused app and can send cleanup text to a cloud provider, even though the skill describes the lab as isolated. Seed $LAB_HOME/.config/pickscribe/config.toml with paste disabled and local-only enabled, or explicitly unset the relevant API/paste environment before launching.
Useful? React with 👍 / 👎.
| ``` | ||
| Start an Xvfb display and a window manager. `xfwm4` is required for maximize/restore controls: | ||
| ```bash | ||
| Xvfb ":$DISPLAY_NUM" -screen 0 1440x1000x24 -nolisten tcp >"/tmp/pickscribe-xvfb-$DISPLAY_NUM.log" 2>&1 & |
There was a problem hiding this comment.
Persist lab variables across command snippets
The lab setup assigns DISPLAY_NUM, PORT, LAB_HOME, and REAL_HOME only in the first fenced command block, but the later Xvfb, Tauri, screenshot, and cleanup snippets rely on those shell variables. When an agent follows the skill with separate exec_command calls (the normal Codex/Claude workflow unless a persistent TTY is explicitly opened), this line runs with an empty display (Xvfb ":"), and the later launch/cleanup commands similarly lose the Vite port and lab home. Persist the values in an env file that each snippet sources, or present the lab flow as one script/session.
Useful? React with 👍 / 👎.
| Basic boot needs no real-profile files. To test dictation, opt in to model-only access before launch: | ||
| ```bash | ||
| mkdir -p "$LAB_HOME/.local/share/whisper.cpp" | ||
| ln -s "$REAL_HOME/.local/share/whisper.cpp/models" "$LAB_HOME/.local/share/whisper.cpp/models" |
There was a problem hiding this comment.
Preserve access to the real whisper-cli for lab dictation
This opt-in only links the real model directory into the lab home. In the common installed setup fixed by src/engine/mod.rs, whisper-cli may live in the real ~/.local/bin and not be on PATH; after the lab launch changes HOME to $LAB_HOME, find_command falls back to $LAB_HOME/.local/bin instead, so dictation tests still fail with “whisper-cli not found” even though the models were linked. Add the real ~/.local/bin to the lab PATH or link the CLI into $LAB_HOME/.local/bin along with the models.
Useful? React with 👍 / 👎.
| for candidate in {1421..1439}; do ! ss -ltnH "sport = :$candidate" | grep -q . && { PORT=$candidate; break; }; done | ||
| : "${PORT:?No free port in 1421-1439}" | ||
|
|
||
| bun run dev -- --port "$PORT" --strictPort >"/tmp/pickscribe-vite-$PORT.log" 2>&1 & |
There was a problem hiding this comment.
Install dependencies before starting the lab frontend
The lab path starts directly with bun run dev, but a clean checkout has no node_modules and this package script depends on Vite from devDependencies; the normal launch includes bun install --frozen-lockfile, while the lab section does not. Agents that choose the isolated lab flow first therefore fail before Tauri starts with the frontend command missing. Add the install step to the lab setup or explicitly require it before this command.
Useful? React with 👍 / 👎.
|
|
||
| Find each PID first with `pgrep -f` (bracketed patterns avoid matching the search). Confirm it is the lab, then run the matching `bash` kill command separately: | ||
| ```bash | ||
| pgrep -af "$PWD/target/debug/[p]ickscribe-app" |
There was a problem hiding this comment.
Scope cleanup to the lab app process
This pgrep matches every pickscribe-app binary built from the current workspace, not just the lab instance. If a normal dev instance is running from the same checkout at the same time (the lab identifier allows both to coexist), the output does not show DISPLAY or $LAB_HOME, so the following kill instruction can terminate the real dev app. Filter by the lab process environment/parent Tauri PID before presenting the PID to kill.
Useful? React with 👍 / 👎.
Seeds a safe lab config (local_only, paste off) and unsets cloud API keys so an empty lab profile cannot paste into the real session or call cloud APIs; whisper-cli/models are an explicit opt-in; lab state persists via /tmp/pickscribe-lab.env for separate shell calls; ss guarded; Xvfb waited on; app kill gated on lab environ; lab identifier suffixed with display number to avoid concurrent-lab single-instance pings. Verified end-to-end: LOCAL-ONLY badge active in the lab screenshot, empty profile, clean teardown.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eb57033083
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| : "${PORT:?No free port in 1421-1439}" | ||
| mkdir -p "$LAB_HOME/.config/pickscribe" | ||
| printf '%s\n' '[general]' 'local_only = true' '' '[paste]' 'method = "none"' 'copy_to_clipboard = false' > "$LAB_HOME/.config/pickscribe/config.toml" | ||
| printf 'REAL_HOME=%s\nPORT=%s\nDISPLAY_NUM=%s\nLAB_HOME=%s\n' "$REAL_HOME" "$PORT" "$DISPLAY_NUM" "$LAB_HOME" > /tmp/pickscribe-lab.env |
There was a problem hiding this comment.
Use a per-lab env file instead of a shared path
When two agents follow this skill concurrently, the second setup overwrites /tmp/pickscribe-lab.env, so the first agent's later start/screenshot/cleanup commands source the wrong LAB_HOME, PORT, and DISPLAY_NUM. That defeats the documented concurrent-lab support and can make an agent inspect or kill another lab while leaving its own processes behind; write the env file under the lab directory or include a unique suffix and use that path consistently.
Useful? React with 👍 / 👎.
| [ -e "/tmp/.X11-unix/X$DISPLAY_NUM" ] || { echo "Xvfb did not start" >&2; false; } | ||
| setsid xfwm4 --display=":$DISPLAY_NUM" --compositor=off </dev/null >"/tmp/pickscribe-xfwm-$DISPLAY_NUM.log" 2>&1 & | ||
| setsid dbus-run-session -- env -u WAYLAND_DISPLAY -u DEEPSEEK_API_KEY -u OPENAI_API_KEY -u OLLAMA_API_KEY -u PICKSCRIBE_API_KEY GDK_BACKEND=x11 DISPLAY=":$DISPLAY_NUM" \ | ||
| HOME="$LAB_HOME" XDG_CONFIG_HOME="$LAB_HOME/.config" XDG_DATA_HOME="$LAB_HOME/.local/share" XDG_CACHE_HOME="$LAB_HOME/.cache" CARGO_HOME="$REAL_HOME/.cargo" RUSTUP_HOME="$REAL_HOME/.rustup" \ |
There was a problem hiding this comment.
Isolate the recorder state directory for labs
When a lab is used for dictation or file transcription while the real app (or another lab) is running, this launch still inherits PICKSCRIBE_STATE_DIR/XDG_RUNTIME_DIR; recorder::state_dir() then writes temp WAVs/logs under the real runtime directory instead of $LAB_HOME. Live recording filenames use only second granularity, so the lab can share or truncate the same state files as another instance and corrupt the experiment; set PICKSCRIBE_STATE_DIR to a lab-owned path while preserving the real runtime dir for PipeWire.
Useful? React with 👍 / 👎.
| Basic boot needs no real-profile files. To test dictation, opt in to model and whisper-cli access before launch: | ||
| ```bash | ||
| source /tmp/pickscribe-lab.env | ||
| WHISPER_CLI=$(command -v whisper-cli) || { echo "whisper-cli required" >&2; false; } |
There was a problem hiding this comment.
Fall back to the standard local whisper-cli path
When whisper-cli was installed by the repo's local installer but $REAL_HOME/.local/bin is not on the agent shell PATH, this command aborts even though the app itself supports that standard location. That prevents lab dictation testing in an otherwise supported setup; try command -v and then $REAL_HOME/.local/bin/whisper-cli/whisper.cpp before failing.
Useful? React with 👍 / 👎.
| ```bash | ||
| source /tmp/pickscribe-lab.env | ||
| setsid bun run dev -- --port "$PORT" --strictPort </dev/null >"/tmp/pickscribe-vite-$PORT.log" 2>&1 & | ||
| for _ in {1..60}; do curl -fsS "http://127.0.0.1:$PORT/" >/dev/null && break; sleep 1; done; curl -fsS "http://127.0.0.1:$PORT/" >/dev/null |
There was a problem hiding this comment.
Stop the launch recipe when readiness checks fail
When Vite never becomes ready (or curl is unavailable), the final curl here returns non-zero but this fenced shell block has no set -e and no || exit, so it continues to start Xvfb and Tauri against an unavailable dev server. The same pattern makes failed prerequisite guards continue, leaving confusing half-started lab processes; make the readiness checks abort the recipe before launching later processes.
Useful? React with 👍 / 👎.
Lab state moves to $PWD/.lab.env (gitignored, one lab per checkout); PICKSCRIBE_STATE_DIR points recorder temp WAVs/logs at the lab home (verified the env var in src/engine/recorder.rs); launch block fails fast when Vite never becomes ready; whisper-cli opt-in falls back to ~/.local/bin when not on PATH.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 83923ae682
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for candidate in {1421..1439}; do ! ss -ltnH "sport = :$candidate" | grep -q . && { PORT=$candidate; break; }; done | ||
| : "${PORT:?No free port in 1421-1439}" | ||
| mkdir -p "$LAB_HOME/.config/pickscribe" | ||
| printf '%s\n' '[general]' 'local_only = true' '' '[paste]' 'method = "none"' 'copy_to_clipboard = false' > "$LAB_HOME/.config/pickscribe/config.toml" |
There was a problem hiding this comment.
Avoid clipboard-only mode in the lab seed config
For lab runs that exercise dictation, this seed does not actually disable output: method = "none" is the app's Clipboard-only mode, and the GUI path calls paste::deliver, which copies whenever cfg.method != "type" even if copy_to_clipboard is false (src/engine/paste.rs:53). Because the lab launch keeps the user's runtime/PATH while only unsetting WAYLAND_DISPLAY, environments with a clipboard helper such as wl-copy can still write dictated text to the live clipboard, contradicting the safe-profile/paste-through guidance; the skill needs a true no-copy/no-paste path or an explicit warning not to test dictation this way.
Useful? React with 👍 / 👎.
method=none is clipboard-only mode and copy_to_clipboard still runs; wl-copy finds the real compositor through the default socket in the inherited runtime dir even with WAYLAND_DISPLAY unset. The lab now gets a private XDG_RUNTIME_DIR (also blocks ydotool), verified by booting the lab and checking the app's environ. Prepare refuses to overwrite live lab state; cleanup guards on missing state.
|
@codex review |
|
Codex Review: Didn't find any major issues. Swish! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Adds a project
runskill so any agent can launch PickScribe — normal dev or an isolated headless lab instance — without rediscovering the launch quirks: Wayland leak isolation (env -u WAYLAND_DISPLAY GDK_BACKEND=x11),.labtestsingle-instance identifier override with separate Vite on a scanned free port, private D-Bus session for the tray icon, Xvfb + xfwm4, inspect-then-kill pid cleanup (nopkill -fin compound commands).Canonical skill at
.agents/skills/run/SKILL.md, with.claude/skills/runas a relative symlink for Claude Code discovery.Tested (by the builder, transcript kept):
bun install --frozen-lockfile; Vite 200 on port 1421, stopped by inspected pid; full lab boot on Xvfb:97with the labtest identifier; dashboard screenshot captured and visually inspected; all five lab processes stopped by inspected pids.Known limitation (documented in the skill): XDG config/data dirs are not keyed on the identifier, so a lab instance sees real user data — the skill forbids destructive UI actions.
Not release-note-worthy: agent tooling only, no app changes (
docs/releases/UNRELEASED.mduntouched).Closes #35
Part of the agent-DX batch: pickforge/pickforge-platform#19