chore: rolling promotion dev -> main - #899
Conversation
️✅ There are no secrets present in this pull request anymore.If these secrets were true positive and are still valid, we highly recommend you to revoke them. 🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request. |
|
Important Review skippedToo many files! This PR contains 239 files, which is 89 over the limit of 150. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (61)
📒 Files selected for processing (239)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a major expansion of the Genie toolkit, including a self-improving metrics agent, a new desktop application package, and a comprehensive set of design 'wishes' and QA specifications. The project's core documentation has been overhauled to prioritize an agent-centric workflow. Review feedback focuses on ensuring cross-platform compatibility for shell utilities, correcting anomalous performance data in the metrics logs, and optimizing JSON parsing efficiency by replacing redundant Python calls with jq.
| local cache_age | ||
| cache_age=$(( $(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0) )) |
There was a problem hiding this comment.
The stat -c %Y command is specific to GNU stat (found on Linux) and will fail on other systems like macOS, which use BSD stat. This will break the caching mechanism on non-Linux environments. You've handled this for the date command elsewhere in the script; a similar cross-platform approach should be used here.
| local cache_age | |
| cache_age=$(( $(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0) )) | |
| local cache_age | |
| if [[ "$(uname)" == "Darwin" ]]; then | |
| cache_age=$(( $(date +%s) - $(stat -f %m "$cache_file" 2>/dev/null || echo 0) )) | |
| else | |
| cache_age=$(( $(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0) )) | |
| fi |
| {"timestamp": "2026-03-24T22:57:37Z", "duration_ms": 4374, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "no_changes", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 34}, {"name": "fetch_releases", "duration_ms": 2985}, {"name": "fetch_prs", "duration_ms": 1063}, {"name": "count_agents", "duration_ms": 53}, {"name": "parse_metrics", "duration_ms": 131}, {"name": "update_readme", "duration_ms": 47}, {"name": "update_state", "duration_ms": 34}, {"name": "commit", "duration_ms": 3}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}} | ||
| {"timestamp": "2026-03-24T22:57:43Z", "duration_ms": 4201, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "no_changes", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 32}, {"name": "fetch_releases", "duration_ms": 2704}, {"name": "fetch_prs", "duration_ms": 1172}, {"name": "count_agents", "duration_ms": 65}, {"name": "parse_metrics", "duration_ms": 122}, {"name": "update_readme", "duration_ms": 43}, {"name": "update_state", "duration_ms": 35}, {"name": "commit", "duration_ms": 3}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.9, "ship_rate_pct": 100.0, "parallel_agents": 3, "updated": "2026-03-24"}} | ||
| {"timestamp": "2026-03-25T00:03:05Z", "duration_ms": 5882, "api_calls": 2, "tools_generated": 0, "tools_available": 10, "errors": [], "status": "success", "fallback": false, "slowest_step": "fetch_releases", "steps": [{"name": "load_state", "duration_ms": 33}, {"name": "fetch_releases", "duration_ms": 3458}, {"name": "fetch_prs", "duration_ms": 1046}, {"name": "count_agents", "duration_ms": 53}, {"name": "parse_metrics", "duration_ms": 1191}, {"name": "update_readme", "duration_ms": 45}, {"name": "update_state", "duration_ms": 30}, {"name": "commit", "duration_ms": 4}], "metrics": {"releases_per_day": 0, "avg_bugfix_time_hours": 1.7, "ship_rate_pct": 100.0, "loc_changed_24h": 7418, "commits_24h": 66, "prs_24h": 28, "parallel_agents": 3, "updated": "2026-03-25T00:03:04Z"}} | ||
| {"timestamp":"2026-03-25T00:00:00Z","duration_ms":31579683120,"api_calls":3,"tools_generated":1,"errors":0,"metrics":{"releases_24h":0,"avg_merge_time_hours":0.7,"ship_rate_pct":96,"merged_prs_7d":48}} |
| releases=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('releases_per_day', 0))") | ||
| avg_time=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('avg_bugfix_time_hours', 0))") | ||
| ship_rate=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('ship_rate_pct', 0))") |
There was a problem hiding this comment.
This script invokes python3 three separate times to parse the same JSON file. This is inefficient and can be hard to maintain. It would be better to parse the JSON file once using a tool like jq, which is already used in other scripts in this agent, for better performance and consistency.
| releases=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('releases_per_day', 0))") | |
| avg_time=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('avg_bugfix_time_hours', 0))") | |
| ship_rate=$(python3 -c "import json; print(json.load(open('$METRICS_FILE')).get('ship_rate_pct', 0))") | |
| releases=$(jq -r '.releases_per_day // 0' "$METRICS_FILE") | |
| avg_time=$(jq -r '.avg_bugfix_time_hours // 0' "$METRICS_FILE") | |
| ship_rate=$(jq -r '.ship_rate_pct // 0' "$METRICS_FILE") |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d95f9f4b44
ℹ️ 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".
| const match = sessions.find((s) => s.name.includes(hint)); | ||
| if (match) return match.name; | ||
| } | ||
| return sessions[0].name; |
There was a problem hiding this comment.
Avoid selecting an arbitrary tmux session outside tmux
When this runs outside a tmux client and no hint matches, it falls back to the first session on the server. The new team create --wish flow persists that value as tmuxSessionName before spawning (spawnLeaderWithWish), so if any unrelated tmux session exists, a newly created team can be bound to and launched into the wrong session/workspace. Returning null when no hint matches (and requiring explicit --session) would prevent cross-project misrouting.
Useful? React with 👍 / 👎.
| const genieBin = process.argv[1] ?? 'genie'; | ||
|
|
||
| const child = spawn(bunPath, [genieBin, 'daemon', 'start'], { |
There was a problem hiding this comment.
Launch daemon from a stable Genie entrypoint
Auto-start derives the executable script from process.argv[1], but in non-CLI contexts (tests, embedded/library use, wrappers) that argument is often not the Genie command file. This causes spawn(bun, [<wrong script>, 'daemon', 'start']), so pgserve never starts and _ensurePgserve eventually fails with the 16s timeout. Resolve the real Genie binary/module path explicitly instead of reusing the current process argv.
Useful? React with 👍 / 👎.
fix: serve never kills agent tmux server + TUI uses clean config
TERM=screen didn't suppress tmux 3.5a probes. New approach: write a shell script that runs clear before exec-ing into tmux attach. Co-authored-by: Genie <genie@automagik.ai>
bun exits immediately after createRoot().render() returns. Wait on the renderer's 'destroy' event (EventEmitter) so the process stays alive while OpenTUI runs, and exits cleanly on Ctrl+Q / SIGTERM.
bun exits immediately after createRoot().render() returns. Wait on the renderer's 'destroy' event (EventEmitter) so the process stays alive while OpenTUI runs, and exits cleanly on Ctrl+Q / SIGTERM. Co-authored-by: Genie <genie@automagik.ai>
Mouse was off in tui-tmux.conf so clicking the left nav pane didn't give it focus. Also select left pane after TUI setup so keyboard input goes to OpenTUI nav by default (not the right terminal pane).
fix(tui): enable mouse + focus left nav pane by default
The green tmux status bar from the genie agent server leaked into the TUI right pane. Set status off on the target session before attaching so only the agent terminal content is visible.
fix(tui): hide agent server status bar in nested attach
Agents spawned from TUI panes inherit GENIE_TUI_PANE=left, causing genie.ts to render the TUI instead of running the command. Only enter TUI mode when there are zero CLI args AND the env var is set.
Only bare `genie` (no args) enters TUI mode. All subcommands (work, spawn, team, etc.) run normally. Clear the env var after the check so child processes never inherit it and accidentally enter TUI mode.
fix(cli): prevent GENIE_TUI_PANE env leak to child processes
serve no longer pre-creates empty bash sessions for every agent. Sessions are created on-demand by genie spawn. TUI Enter handler spawns the agent if not running, then attaches. Co-authored-by: Genie <genie@automagik.ai>
) * fix(tui): stop creating empty agent sessions, spawn on Enter serve no longer pre-creates empty bash sessions for every agent. Sessions are created on-demand by genie spawn. TUI Enter handler spawns the agent if not running, then attaches. * fix(tui): keybindings, no empty sessions, Ctrl+Q detaches - Tab toggles between nav and terminal (bidirectional, was one-way) - Ctrl+Q detaches from TUI (was killing session) - Remove Ctrl+T send-keys to nested tmux (broken, will be reimplemented) - Remove startAgentTmuxServer from serve (no empty sessions) - Enter spawns agent if not running, then attaches --------- Co-authored-by: Genie <genie@automagik.ai>
- tmux.ts: Use default tmux server for TUI session (no -L genie-tui),
agents stay on -L genie. Add while-true loop in respawn-pane so
right pane survives agent exit instead of dying.
- serve.ts: Remove TUI_SOCKET and tuiTmuxConf — TUI session lives on
default server. Kill only the session on shutdown, not a server.
- Nav.tsx: Resolve agent CWD from GENIE_TUI_WORKSPACE/agents/{name}
and pass to child_process.spawn for correct working directory.
fix(tui): restore default tmux server, fix agent CWD, add pane survival
… metrics - Use `systeminformation` for proper CPU per-core load and memory stats - RAM now shows active memory (excludes buffers/cache), not total-free - CPU shows combined % + per-core heatmap with color-coded blocks (green < 20%, emerald < 50%, amber < 80%, red > 80%) - Load humanized as % of cores (e.g. "36% (30/84 cores)") - Version shown on first render (before stats load) - Swap hidden when none configured
fix(tui): system stats v2 — accurate hw metrics with per-core heatmap
Column names from user-supplied JSON were interpolated into SQL with double-quote quoting but no validation, allowing injection via crafted keys like: foo"; DROP TABLE x; -- Add assertValidColumnName() that rejects any column name not matching /^[a-zA-Z_][a-zA-Z0-9_]*$/ (strict allowlist approach). Called in prepareRow() before columns touch SQL. Closes #955
- Every line uses <box height={1}> (fixes text overlap / "CPUie23%")
- ASCII bar [===-----] instead of Unicode blocks (fixes width corruption)
- Top-3 hot cores replaces 84-span heatmap (readable, meaningful)
- RAM uses si.mem().active (excludes buffers/cache)
- Load humanized as "28% (24/84 busy)"
- Version renders before stats load
- Remove GENIE_APP check from db.ts: only GENIE_IS_DAEMON spawns pgserve - autoStartDaemon() now launches `genie serve --headless` instead of `genie daemon start` - Add --headless flag to `genie serve` (services only, no TUI) - `genie daemon start` redirects to `genie serve --headless` with deprecation notice - `genie daemon stop/status/logs` check both serve.pid and scheduler.pid - systemd unit now points to `genie serve start --headless --foreground` - Add service-registry.ts for PID tracking and orphan cleanup - Enhanced shutdown: proper ordering, 10s force-kill timeout, pgserve lockfile cleanup
refactor: unify serve + daemon — single process owns the stack
…tion fix(security): sanitize column names in import prepareRow()
fix(tui): v3 system stats — fix garbled rendering
When `genie spawn` is called for an agent that previously had a running
Claude Code session (now dead), it always created a new session, losing
all conversation context. This adds auto-resume detection in
handleWorkerSpawn before rejectDuplicateRole cleans up the dead worker
entry, preserving the claudeSessionId for resume.
Also fixes sessionExists() in team-lead-command.ts to check for the
team-prefixed session name format ({name}-{name}) that Claude Code
actually stores.
…xity Moves the dead-worker detection logic from inline in handleWorkerSpawn into a standalone findDeadResumable() helper, keeping the function under the lint complexity threshold.
fix: auto-resume Claude session on genie spawn
Bug 1: `console.log(JSON.stringify(tasks, null, 2))` truncates at 8192 bytes due to Node/Bun console.log buffering limits. Replace with `process.stdout.write()` which handles unbounded output. When piped (non-TTY), omit pretty-printing for compact output. Bug 2: `buildScopeConditions` checked `allProjects` before `projectName` in an if/else chain, so --all silently dropped --project filtering. The --all flag means "all statuses" not "all projects" when a project is explicitly specified. Reorder the conditions so projectName is checked first in both `buildScopeConditions` and `listTasksForActor`. Closes #971
Both genie.tmux.conf (inner/agent layer) and tui-tmux.conf (outer/TUI layer) used `set-clipboard on`, which causes tmux to consume OSC 52 sequences instead of forwarding them to the terminal emulator. This breaks clipboard sync for SSH users connecting through the TUI. Changes: - Switch both configs from `set-clipboard on` to `set-clipboard external` - Add `allow-passthrough on` and Ms terminal-override to genie.tmux.conf - Create osc52-copy.sh helper that writes OSC 52 directly to SSH PTY - Replace copy-selection-and-cancel with copy-pipe-and-cancel bindings that pipe through osc52-copy.sh in both configs - Update update.ts and setup.ts to install osc52-copy.sh to ~/.genie/ Closes #967
fix(tmux): enable OSC 52 clipboard passthrough in nested tmux layers
SKILL.md documented 7 of 21+ commands. Now covers all 6 categories: - Lifecycle: init, create, archive, migrate - Ingest: update, process, watch, mount, unmount - Query: search (with --strategy/--explain), get, analyze - Knowledge: link (10 types), health (7 dimensions), status - Identity: attach, detach, list (RBAC with owner/writer/reader) - Observability: traces, strategy routing, cache estimation Also adds: RAG vs CAG strategy docs, confidence thresholds with exact numeric ranges, mount system, agent guidance for new features.
docs: update /brain skill to cover full genie-brain v0.1.0 API
…ion-clean fix(task): resolve JSON truncation at 8KB + honor --project with --all
CC's native team permission gate uses the global `teammateMode` setting in ~/.claude/settings.json as the authority for teammate permissions. When set to "auto", tool approvals route to the team leader — which is an AI agent that can't approve, causing a deadlock where subagents hang forever waiting for Edit/Write permission. Genie already passes `--permission-mode bypassPermissions` per-session (provider-adapters.ts:251), but the global setting overrides it for the native team layer. This adds `ensureTeammateBypassPermissions()` to claude-settings.ts and calls it from `ensureNativeTeam()` so the global setting is always correct when teams are active. Also ensures `skipDangerousModePermissionPrompt: true` so agents spawned with `--dangerously-skip-permissions` don't hit interactive prompts.
…-permissions fix: ensure teammateMode=bypassPermissions on native team setup
buildResumeParams was missing the model field, causing agents to fall back to the default model on resume instead of keeping their configured model from the agent directory.
fix: preserve model field on agent resume
Rolling Promotion PR
Auto-maintained rolling promotion PR from
devtomain.Process:
ready-to-mergeadded when all checks pass