chore: rolling promotion dev -> main - #650
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughBumps package/plugin versions to 3.260317.20, adds/updates tmux integration scripts and config, syncs tmux assets from the update command, shifts session naming and agent identity to a team-lead model, and replaces git worktree workflows with shared clone logic. Changes
Sequence Diagram(s)sequenceDiagram
participant Tmux as Tmux
participant GProj as genie-projects.sh
participant GLabel as genie-window-label.sh
participant GSessions as genie-sessions.sh
participant Workers as workers.json
participant Update as src/genie-commands/update.ts
participant Display as TmuxStatus
Tmux->>GProj: Request render session list
GProj->>Workers: Read and merge sessions (tmux + workers.json)
GProj->>GProj: Build all_sessions, decide full vs overflow
alt total ≤ max_visible
GProj->>Display: Render all sessions (with labels)
else total > max_visible
GProj->>Display: Render up to max_visible (reserve/ensure active)
GProj->>Display: Append overflow indicator (+N)
end
Tmux->>GLabel: Query(session,window) for enrichment
GLabel->>Workers: Filter & compute count/worst-state
GLabel-->>Display: " ×<count> <emoji>" or nothing
Tmux->>GSessions: Request formatted session list
GSessions-->>Display: Styled session list (highlights active)
Update->>Tmux: After syncing tmux assets, attempt tmux reload (non-fatal)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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 |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request facilitates the automated promotion of the 'genie' plugin, specifically updating its version across various configuration and package manifest files. It is a routine part of the continuous integration and deployment pipeline, ensuring that the Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request is an automated rolling promotion from the dev branch to main. The changes consist of version bumps across several package and plugin configuration files. While the version updates are consistent, I've identified a potential point of fragility in the versioning script's logic for updating the marketplace manifest, which could be made more robust to prevent future issues.
| { | ||
| "name": "genie", | ||
| "version": "3.260317.18", | ||
| "version": "3.260317.20", |
There was a problem hiding this comment.
The versioning script (scripts/version.ts) updates this version by targeting the first plugin in the plugins array (plugins[0]). This approach is brittle and may lead to incorrect versioning if more plugins are added to this file or if their order changes. To make the update process more robust, the script should be modified to locate the 'genie' plugin by its name property before updating the version.
…chment
Fix broken awk $1/$2 escaping in version guard that prevented dual bar
from activating. Replace genie-tasks.sh script call in status-format[1]
with native #{W} token for clickable window tabs. Add per-window agent
enrichment via genie-window-label.sh called from window-status-format
templates. Add overflow handling to genie-projects.sh for >8 sessions.
fix(tmux): restore dual status bar with clickable tabs and agent enrichment
syncTmuxScripts now also copies genie.tmux.conf to ~/.genie/scripts/, overwrites ~/.tmux.conf when it contains the genie header, and reloads tmux automatically. Extracted syncTmuxConf helper to keep complexity within lint limits.
fix(tmux): sync genie.tmux.conf and auto-reload on update
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/tmux/genie-projects.sh`:
- Line 47: Remove the dead variable assignment and any writes to it: delete the
`active_output=""` initialization and the other write at the second occurrence
(around the other mentioned line) so the first tmux pass only populates
`all_sessions` without introducing unused state; search for the symbol
`active_output` and remove those lines (and any associated, now-unused
references) to avoid writing an unused path.
- Around line 95-100: The reservation logic incorrectly reserves the last slot
even when active_session is empty; update the conditional that currently checks
shown, active_shown, and session_name != active_session to also require that
active_session is non-empty (e.g., test -n "$active_session" or
"$active_session" != ""), so the "reserve last slot for active session" branch
only runs when an active session exists; apply the same change to the other
occurrence that mirrors this logic (the block around the second instance at
lines referenced in the comment).
In `@scripts/tmux/genie.tmux.conf`:
- Around line 83-86: The tmux status formats call the external script via #()
with unescaped format variables (see window-status-format and
window-status-current-format using #{session_name} and #{window_name}), which
can break when names contain spaces or shell metacharacters; update both formats
to use the tmux format-escape modifier #{q:...} for those variables (i.e.,
replace #{session_name} and #{window_name} with #{q:session_name} and
#{q:window_name}) so the arguments are safely quoted when invoking
$HOME/.genie/scripts/genie-window-label.sh.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: b68d45ce-fc00-4140-878f-4550ce388efd
📒 Files selected for processing (8)
.claude-plugin/marketplace.jsonopenclaw.plugin.jsonpackage.jsonplugins/genie/.claude-plugin/plugin.jsonplugins/genie/package.jsonscripts/tmux/genie-projects.shscripts/tmux/genie-window-label.shscripts/tmux/genie.tmux.conf
| output="" | ||
| declare -A seen_sessions=() | ||
| declare -a all_sessions=() | ||
| active_output="" |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Remove the dead active_output path.
active_output is written once and never read, so this first tmux pass looks stateful when it only populates all_sessions.
Also applies to: 60-60
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/tmux/genie-projects.sh` at line 47, Remove the dead variable
assignment and any writes to it: delete the `active_output=""` initialization
and the other write at the second occurrence (around the other mentioned line)
so the first tmux pass only populates `all_sessions` without introducing unused
state; search for the symbol `active_output` and remove those lines (and any
associated, now-unused references) to avoid writing an unused path.
| if [[ "$shown" -ge "$((max_visible - 1))" && "$active_shown" == "false" && "$session_name" != "$active_session" ]]; then | ||
| # Reserve last slot for active session | ||
| continue | ||
| fi | ||
| if [[ "$shown" -ge "$max_visible" ]]; then | ||
| break |
There was a problem hiding this comment.
Don't reserve an active-session slot when active_session is empty.
In the no-tmux/testing path, active_session stays empty. Once shown reaches 7, this guard skips every remaining session, so you render only 7 entries while +N more is still computed as if 8 were shown.
Suggested fix
- if [[ "$shown" -ge "$((max_visible - 1))" && "$active_shown" == "false" && "$session_name" != "$active_session" ]]; then
+ if [[ -n "$active_session" && "$shown" -ge "$((max_visible - 1))" && "$active_shown" == "false" && "$session_name" != "$active_session" ]]; then
# Reserve last slot for active session
continue
fiAlso applies to: 128-130
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/tmux/genie-projects.sh` around lines 95 - 100, The reservation logic
incorrectly reserves the last slot even when active_session is empty; update the
conditional that currently checks shown, active_shown, and session_name !=
active_session to also require that active_session is non-empty (e.g., test -n
"$active_session" or "$active_session" != ""), so the "reserve last slot for
active session" branch only runs when an active session exists; apply the same
change to the other occurrence that mirrors this logic (the block around the
second instance at lines referenced in the comment).
| set -g window-status-format "#[fg=#b8a9c9,bg=#16213e] #I:#W#($HOME/.genie/scripts/genie-window-label.sh #{session_name} #{window_name}) " | ||
|
|
||
| # Active tab: purple bg, white text, bold | ||
| set -g window-status-current-format "#[fg=#1a1a2e,bg=#7b2ff7]#[fg=#e0e0e0,bg=#7b2ff7,bold] #I:#W #[fg=#7b2ff7,bg=#1a1a2e]" | ||
| # Active tab: purple bg, white text, bold with agent enrichment | ||
| set -g window-status-current-format "#[fg=#16213e,bg=#7b2ff7]#[fg=#e0e0e0,bg=#7b2ff7,bold] #I:#W#($HOME/.genie/scripts/genie-window-label.sh #{session_name} #{window_name}) #[fg=#7b2ff7,bg=#16213e]" |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In tmux #(...)command substitutions, what is the recommended way to shell-escape#{session_name}and#{window_name} or pass them as positional arguments so spaces and shell metacharacters are preserved safely?
💡 Result:
The recommended way to safely pass tmux format variables like #{session_name} and #{window_name} to shell commands in #(shell-command) substitutions, preserving spaces and shell metacharacters, is to use the #{q:variable} format modifier. This modifier quotes the variable for safe use in shell commands by escaping special characters (e.g., wrapping in single quotes and escaping internal single quotes). For example, in a status line or run-shell command: status-right "#(my-script '#{q:session_name}' '#{q:window_name}')" This expands #{session_name} and #{window_name} safely before passing to the shell command inside #. If passing as positional arguments to a script, the script receives them as argv[1], argv[2], etc., with original content preserved (spaces intact, metacharacters escaped for shell parsing). Alternative for complex cases: Use display-message -p '#{session_name}' in a wrapper script to retrieve values safely via stdout, avoiding direct substitution. tmux does not automatically shell-escape plain #{variable}; using #{q:} is the official recommended approach per tmux source discussions and format documentation.
Citations:
- 1: https://github.com/tmux/tmux/wiki/Formats
- 2: Double escaping of quotation marks in command-prompt template tmux/tmux#2528
- 3: [feature request] Add an option to expand "formats" in an quoted string (at command parsing time, not at command execution time) tmux/tmux#2514
- 4: https://man7.org/linux/man-pages/man1/tmux.1.html
- 5: https://man.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man1/tmux.1
🏁 Script executed:
cat -n scripts/tmux/genie.tmux.conf | sed -n '80,90p'Repository: automagik-dev/genie
Length of output: 728
Use #{q:...} to safely escape tmux format variables in shell commands.
Lines 83 and 86 pass #{session_name} and #{window_name} unescaped into a shell command via #(). Names with spaces will split the argv; shell metacharacters can be evaluated on every refresh. Use the #{q:variable} format modifier to escape these safely:
set -g window-status-format "#[fg=#b8a9c9,bg=#16213e] `#I`:`#W`#($HOME/.genie/scripts/genie-window-label.sh #{q:session_name} #{q:window_name}) "
set -g window-status-current-format "#[fg=#16213e,bg=#7b2ff7]#[fg=#e0e0e0,bg=#7b2ff7,bold] `#I`:`#W`#($HOME/.genie/scripts/genie-window-label.sh #{q:session_name} #{q:window_name}) #[fg=#7b2ff7,bg=#16213e]"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/tmux/genie.tmux.conf` around lines 83 - 86, The tmux status formats
call the external script via #() with unescaped format variables (see
window-status-format and window-status-current-format using #{session_name} and
#{window_name}), which can break when names contain spaces or shell
metacharacters; update both formats to use the tmux format-escape modifier
#{q:...} for those variables (i.e., replace #{session_name} and #{window_name}
with #{q:session_name} and #{q:window_name}) so the arguments are safely quoted
when invoking $HOME/.genie/scripts/genie-window-label.sh.
# Conflicts: # .claude-plugin/marketplace.json # openclaw.plugin.json # package.json # plugins/genie/.claude-plugin/plugin.json # plugins/genie/package.json # scripts/tmux/genie-projects.sh # scripts/tmux/genie.tmux.conf
- Session name derived from folder name (not hardcoded 'genie') - Running genie in tmux tab launches CC in current pane, not new window - agentType set to 'team-lead' for self-approval authority - Bottom bar shows all sessions as "Agents:" via pane-border - Ctrl+T opens new tab with session start directory - Ctrl+(/Ctrl+) switches sessions
- Added name field to SpawnParams — sets CC session display name - buildClaudeCommand() emits --name <name> when provided - Team-lead command builder passes --name <teamName> - Worker spawn sets name to <team>-<role> (e.g., fix-parser-engineer-1) - resume field accepts strings (not just UUIDs) for name-based resume
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
scripts/tmux/genie.tmux.conf (1)
85-85:⚠️ Potential issue | 🟠 MajorShell-escape
#{session_name}in the#()command.Line 85 passes
#{session_name}unescaped into shell execution. Session names with spaces/metacharacters can break argument parsing or execute unintended shell syntax.Suggested fix
-set -g pane-border-format "#[align=left,bg=#16213e,fg=#b8a9c9] Agents: #($HOME/.genie/scripts/genie-sessions.sh #{session_name})" +set -g pane-border-format "#[align=left,bg=#16213e,fg=#b8a9c9] Agents: #($HOME/.genie/scripts/genie-sessions.sh #{q:session_name})"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/tmux/genie.tmux.conf` at line 85, The pane-border-format currently injects raw #{session_name} into a shell command which can break or allow injection; change the command so the session name is shell-escaped when invoking .genie/scripts/genie-sessions.sh (e.g. use tmux's shell-escape format modifier for the session name such as "#{session_name@sh}" or similar supported :sh modifier) so the line calling genie-sessions.sh passes a quoted/escaped argument (reference symbols: pane-border-format, #{session_name}, and .genie/scripts/genie-sessions.sh).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/tmux/genie-sessions.sh`:
- Around line 11-17: The loop over tmux sessions is splitting session names on
whitespace because it uses for sess in $(tmux list-sessions ...); to fix this,
read the session names line-by-line instead (e.g., pipe tmux list-sessions -F
'#{session_name}' ... | sort into a while IFS= read -r sess loop or use mapfile
-t and iterate the array) so the variable sess preserves spaces and special
characters; update the block that references sess, current_session and appends
to output accordingly to use the line-oriented reader.
In `@src/genie-commands/session.ts`:
- Line 323: The fallback for sessionName currently uses
sanitizeWindowName(basename(workspaceDir)) which creates per-folder sessions;
change the expression so options.name falls back to the existing shared tmux
session constant used elsewhere (instead of basename(workspaceDir)). Edit the
assignment to sessionName (the line using options.name ?? ...) to use that
shared session constant as the default, removing the
sanitizeWindowName(basename(workspaceDir)) fallback to restore the single shared
session behavior.
- Around line 339-347: The inside-tmux branch must use the canonical
deriveWindowName and same registration/env setup as other flows instead of
renaming the current client window with a timestamp suffix; replace the
suffix/currentWindowName logic with a call to deriveWindowName(sessionName?) to
produce the authoritative windowName, remove the ad-hoc Date.now suffix, call
ensureNativeTeamForLeader(derivedWindowName, workspaceDir) and compute
resumeSessionId via findLastSessionId(sanitizeTeamName(derivedWindowName),
agentName, workspaceDir), and ensure the GENIE_CWD window env and registry
registration steps used by other startup paths are executed before building the
command (buildClaudeCommand) so the inside-tmux path remains deterministic and
resumes the correct team/session.
- Around line 348-349: The code uses a runtime require to get execSync which
fails in ESM; instead add a static import for execSync from 'node:child_process'
alongside the existing imports at the top of the file, remove the require(...)
at the call site, and call execSync(cmd, { stdio: 'inherit', cwd: workspaceDir
}) directly where execSyncCmd is currently used (referencing execSync, cmd, and
workspaceDir to find the call).
---
Duplicate comments:
In `@scripts/tmux/genie.tmux.conf`:
- Line 85: The pane-border-format currently injects raw #{session_name} into a
shell command which can break or allow injection; change the command so the
session name is shell-escaped when invoking .genie/scripts/genie-sessions.sh
(e.g. use tmux's shell-escape format modifier for the session name such as
"#{session_name@sh}" or similar supported :sh modifier) so the line calling
genie-sessions.sh passes a quoted/escaped argument (reference symbols:
pane-border-format, #{session_name}, and .genie/scripts/genie-sessions.sh).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 7badd5b1-b42a-4be9-b9bb-48a07ed76d15
📒 Files selected for processing (3)
scripts/tmux/genie-sessions.shscripts/tmux/genie.tmux.confsrc/genie-commands/session.ts
| for sess in $(tmux list-sessions -F '#{session_name}' 2>/dev/null | sort); do | ||
| if [ "$sess" = "$current_session" ]; then | ||
| output+="#[bg=#7b2ff7,fg=#e0e0e0,bold] $sess #[bg=#16213e,fg=#7b2ff7,nobold]" | ||
| else | ||
| output+="#[fg=#b8a9c9,bg=#16213e] $sess " | ||
| fi | ||
| done |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd -t f genie-sessions.shRepository: automagik-dev/genie
Length of output: 93
🏁 Script executed:
file_path=$(fd -t f genie-sessions.sh 2>/dev/null | head -1)
if [ -n "$file_path" ]; then
echo "File found: $file_path"
wc -l "$file_path"
head -30 "$file_path" | cat -n
fiRepository: automagik-dev/genie
Length of output: 839
🌐 Web query:
tmux session names allowed characters spaces special characters constraints
💡 Result:
Tmux session names must be non-empty and cannot contain the characters ':' or '.'. Spaces and other special characters are allowed as long as they are not ':' or '.'. There is no defined maximum length. When creating or renaming a session, tmux validates the name using session_check_name, which returns an error if the name is empty or contains ':' or '.'. In the source code, this check is: return (*name != '\0' && name[strcspn(name, ":.")] == '\0');. If invalid, commands like new-session or rename-session report "bad session name". Colons and periods are prohibited because they are special characters used in tmux targets to separate session:window and window.pane (e.g., mysession:mywindow.1). Tabs are allowed in older versions but sanitized to spaces in tmux 3.2+. Control characters may cause issues like hanging (fixed in 3.0+), but are not explicitly forbidden beyond validation. Examples: - Valid: "my session", "test-123", "user@host" - Invalid: "", "my:session", "my.session", "session:name" Official tmux man pages do not explicitly document these constraints, but they are enforced in the code and confirmed by tmux maintainer in GitHub issues.
Citations:
- 1: Question: safe delimiters to use when parsing
tmux list-*commands tmux/tmux#2179 - 2: Why we can not you "." in session title? tmux/tmux#2524
- 3: https://github.com/tmux/tmux/blob/master/session.c
- 4: https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/tmux/session.c?annotate=1.46
- 5: Question: Maximum session name length supported ? tmux/tmux#3113
- 6: "tmux rename-session" with control character makes tmux hang tmux/tmux#1996
Avoid word-splitting session names during iteration.
Line 11 uses command substitution in a for loop, which breaks on whitespace in session names and can render incorrect labels.
Suggested fix
output=""
-for sess in $(tmux list-sessions -F '#{session_name}' 2>/dev/null | sort); do
+while IFS= read -r sess; do
if [ "$sess" = "$current_session" ]; then
output+="#[bg=#7b2ff7,fg=#e0e0e0,bold] $sess #[bg=#16213e,fg=#7b2ff7,nobold]"
else
output+="#[fg=#b8a9c9,bg=#16213e] $sess "
fi
-done
+done < <(tmux list-sessions -F '#{session_name}' 2>/dev/null | sort)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/tmux/genie-sessions.sh` around lines 11 - 17, The loop over tmux
sessions is splitting session names on whitespace because it uses for sess in
$(tmux list-sessions ...); to fix this, read the session names line-by-line
instead (e.g., pipe tmux list-sessions -F '#{session_name}' ... | sort into a
while IFS= read -r sess loop or use mapfile -t and iterate the array) so the
variable sess preserves spaces and special characters; update the block that
references sess, current_session and appends to output accordingly to use the
line-oriented reader.
| export async function sessionCommand(options: SessionOptions = {}): Promise<void> { | ||
| const sessionName = options.name ?? DEFAULT_SESSION_NAME; | ||
| const workspaceDir = options.dir ?? process.cwd(); | ||
| const sessionName = options.name ?? sanitizeWindowName(basename(workspaceDir)); |
There was a problem hiding this comment.
Revert default sessionName to the shared constant.
Defaulting to basename(workspaceDir) shifts behavior from one shared tmux session to per-folder sessions, which conflicts with this command’s documented session model and changes reset/window-collision semantics.
Proposed fix
- const sessionName = options.name ?? sanitizeWindowName(basename(workspaceDir));
+ const sessionName = options.name ?? DEFAULT_SESSION_NAME;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const sessionName = options.name ?? sanitizeWindowName(basename(workspaceDir)); | |
| const sessionName = options.name ?? DEFAULT_SESSION_NAME; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/genie-commands/session.ts` at line 323, The fallback for sessionName
currently uses sanitizeWindowName(basename(workspaceDir)) which creates
per-folder sessions; change the expression so options.name falls back to the
existing shared tmux session constant used elsewhere (instead of
basename(workspaceDir)). Edit the assignment to sessionName (the line using
options.name ?? ...) to use that shared session constant as the default,
removing the sanitizeWindowName(basename(workspaceDir)) fallback to restore the
single shared session behavior.
| } else if (process.env.TMUX) { | ||
| // Already inside tmux — launch Claude Code in the CURRENT pane | ||
| const suffix = Date.now().toString(36).slice(-4); | ||
| const currentWindowName = `${windowName}-${suffix}`; | ||
| await tmux.executeTmux(`rename-window ${shellQuote(currentWindowName)}`); | ||
| await ensureNativeTeamForLeader(currentWindowName, workspaceDir); | ||
| const agentName = basename(workspaceDir); | ||
| const resumeSessionId = findLastSessionId(sanitizeTeamName(currentWindowName), agentName, workspaceDir); | ||
| const cmd = buildClaudeCommand(currentWindowName, systemPromptFile || undefined, resumeSessionId || undefined); |
There was a problem hiding this comment.
Inside-tmux flow can target the wrong window and fragments team identity.
rename-window here acts on the current client window (not explicitly bound to sessionName), and the timestamp suffix creates a new team/window identity every run, which breaks deterministic resume/team continuity. This path also skips the GENIE_CWD window env and registry registration used by other startup paths.
Proposed fix
} else if (process.env.TMUX) {
- // Already inside tmux — launch Claude Code in the CURRENT pane
- const suffix = Date.now().toString(36).slice(-4);
- const currentWindowName = `${windowName}-${suffix}`;
- await tmux.executeTmux(`rename-window ${shellQuote(currentWindowName)}`);
+ // Already inside tmux
+ const currentSession = (await tmux.executeTmux("display-message -p '#S'")).trim();
+ if (currentSession !== sessionName) {
+ await focusTeamWindow(sessionName, windowName, workspaceDir, systemPromptFile);
+ attachToWindow(sessionName, windowName);
+ return;
+ }
+ const currentWindowName = windowName;
+ await tmux.executeTmux(`rename-window ${shellQuote(currentWindowName)}`);
+ await tmux.setWindowEnv(`${sessionName}:${currentWindowName}`, 'GENIE_CWD', workspaceDir);
await ensureNativeTeamForLeader(currentWindowName, workspaceDir);
const agentName = basename(workspaceDir);
const resumeSessionId = findLastSessionId(sanitizeTeamName(currentWindowName), agentName, workspaceDir);
+ await registerSessionInRegistry(sessionName, currentWindowName, workspaceDir);
const cmd = buildClaudeCommand(currentWindowName, systemPromptFile || undefined, resumeSessionId || undefined);Based on learnings: deriveWindowName is the single entry point for window name resolution and should remain the authoritative naming path.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/genie-commands/session.ts` around lines 339 - 347, The inside-tmux branch
must use the canonical deriveWindowName and same registration/env setup as other
flows instead of renaming the current client window with a timestamp suffix;
replace the suffix/currentWindowName logic with a call to
deriveWindowName(sessionName?) to produce the authoritative windowName, remove
the ad-hoc Date.now suffix, call ensureNativeTeamForLeader(derivedWindowName,
workspaceDir) and compute resumeSessionId via
findLastSessionId(sanitizeTeamName(derivedWindowName), agentName, workspaceDir),
and ensure the GENIE_CWD window env and registry registration steps used by
other startup paths are executed before building the command
(buildClaudeCommand) so the inside-tmux path remains deterministic and resumes
the correct team/session.
| const { execSync: execSyncCmd } = require('node:child_process'); | ||
| execSyncCmd(cmd, { stdio: 'inherit', cwd: workspaceDir }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "package.json module type:"
jq -r '.type // "undefined"' package.json
echo
echo "tsconfig module settings:"
fd -a '^tsconfig.*\.json$' | while read -r f; do
echo "--- $f"
jq -r '.compilerOptions.module // "undefined"' "$f" 2>/dev/null || true
done
echo
echo "dynamic require() usages under src/genie-commands:"
rg -nP --type=ts -C2 '\brequire\s*\(' src/genie-commandsRepository: automagik-dev/genie
Length of output: 1151
🏁 Script executed:
head -50 src/genie-commands/session.ts | cat -nRepository: automagik-dev/genie
Length of output: 2020
🏁 Script executed:
rg -nA5 -B5 "^import.*from.*node:child_process" src/genie-commands/session.tsRepository: automagik-dev/genie
Length of output: 637
🏁 Script executed:
sed -n '345,352p' src/genie-commands/session.ts | cat -nRepository: automagik-dev/genie
Length of output: 657
Use static import for execSync instead of runtime require().
In ESM mode ("type": "module"), dynamic require() at runtime (line 348) is unreliable and can fail. Add execSync to the existing import from node:child_process at line 14 and call it directly.
Proposed fix
-import { spawnSync } from 'node:child_process';
+import { execSync, spawnSync } from 'node:child_process';Then at line 348–349:
- const { execSync: execSyncCmd } = require('node:child_process');
- execSyncCmd(cmd, { stdio: 'inherit', cwd: workspaceDir });
+ execSync(cmd, { stdio: 'inherit', cwd: workspaceDir });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { execSync: execSyncCmd } = require('node:child_process'); | |
| execSyncCmd(cmd, { stdio: 'inherit', cwd: workspaceDir }); | |
| execSync(cmd, { stdio: 'inherit', cwd: workspaceDir }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/genie-commands/session.ts` around lines 348 - 349, The code uses a
runtime require to get execSync which fails in ESM; instead add a static import
for execSync from 'node:child_process' alongside the existing imports at the top
of the file, remove the require(...) at the call site, and call execSync(cmd, {
stdio: 'inherit', cwd: workspaceDir }) directly where execSyncCmd is currently
used (referencing execSync, cmd, and workspaceDir to find the call).
Without this flag, CC routes permission requests to a nonexistent team leader instead of self-approving. The agentType in native team config wasn't enough — CC needs the CLI flag directly.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/lib/team-lead-command.ts (1)
44-54:⚠️ Potential issue | 🔴 CriticalFix the identity mismatch: change
GENIE_AGENT_NAMEto'team-lead'and update the corresponding test.Line 49 sets
GENIE_AGENT_NAMEto the folder name, but lines 51–54 identify this process asteam-lead(via--agent-id,--agent-name, and--agent-type). The env var must match the CLI flags for Claude Code native team recognition to work.The test at
src/genie-commands/__tests__/session.test.ts:31-34explicitly expectsGENIE_AGENT_NAMEto equal the folder name. This test asserts the buggy behavior and will break after the fix — it must be updated to expect'team-lead'.Also, the comment at line 36 ("The agent name is derived from basename(cwd) to match the folder name") contradicts the actual requirement; it should be removed or corrected.
🔧 Proposed changes
In
src/lib/team-lead-command.ts:const folderName = basename(process.cwd()); const parts = [ @@ - `GENIE_AGENT_NAME=${shellQuote(folderName)}`, + `GENIE_AGENT_NAME=${shellQuote('team-lead')}`,Remove or correct the misleading comment at line 36.
In
src/genie-commands/__tests__/session.test.ts:test('sets GENIE_AGENT_NAME env var to folder name', () => { const cmd = buildClaudeCommand('genie'); - expect(cmd).toContain(`GENIE_AGENT_NAME='${folderName}'`); + expect(cmd).toContain(`GENIE_AGENT_NAME='team-lead'`); });Consider renaming the test to reflect the actual purpose.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/team-lead-command.ts` around lines 44 - 54, Change the environment var GENIE_AGENT_NAME to the literal 'team-lead' so it matches the CLI flags (--agent-name team-lead / --agent-type team-lead / --agent-id team-lead@...) instead of using folderName; update the parts array initialization (where folderName, shellQuote, qTeam are used) to set `GENIE_AGENT_NAME='team-lead'`, remove or correct the misleading comment about deriving the name from basename(cwd) in team-lead-command.ts, and update the test in src/genie-commands/__tests__/session.test.ts (which currently expects folderName) to assert 'team-lead' (and optionally rename the test to reflect the new expectation).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/lib/team-lead-command.ts`:
- Around line 44-54: Change the environment var GENIE_AGENT_NAME to the literal
'team-lead' so it matches the CLI flags (--agent-name team-lead / --agent-type
team-lead / --agent-id team-lead@...) instead of using folderName; update the
parts array initialization (where folderName, shellQuote, qTeam are used) to set
`GENIE_AGENT_NAME='team-lead'`, remove or correct the misleading comment about
deriving the name from basename(cwd) in team-lead-command.ts, and update the
test in src/genie-commands/__tests__/session.test.ts (which currently expects
folderName) to assert 'team-lead' (and optionally rename the test to reflect the
new expectation).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c675fb6b-dcf3-43ff-97d0-181fefe55ad5
📒 Files selected for processing (5)
scripts/tmux/genie.tmux.confsrc/genie-commands/__tests__/session.test.tssrc/lib/provider-adapters.tssrc/lib/team-lead-command.tssrc/term-commands/agents.ts
* fix(team): replace git worktree with git clone --shared git worktree shares .git metadata with the parent repo. CC workers running git operations inside the worktree can flip core.bare=true on the parent, silently corrupting it. git clone --shared reuses the same object store (fast, no disk duplication) but gets its own .git directory, eliminating the core.bare corruption vector entirely. Changes: - ensureWorktree(): create branch in parent then git clone --shared - disbandTeam(): rm -rf the clone dir instead of git worktree remove - pruneStaleWorktrees(): remove git worktree prune and core.bare fix - genie.ts: keep startup guard as legacy safety net with updated comment - Update tests to use git clone --shared instead of git worktree * fix(team): inherit git user config in shared clones git clone --shared creates a separate .git, so local user config from the parent repo doesn't carry over. CI runners without global git config would fail on commits inside the clone. Copy user.name and user.email from parent repo after cloning. --------- Co-authored-by: Genie <genie@automagik.ai>
Emergency: P0 core.bare fix includedThis rolling promotion now includes the emergency fix for the core.bare corruption bug (#634, PR #655). What changed: Replaced all Risk: Low — net code deletion, 752/752 tests pass, CI green. Request: Please merge this to main ASAP for emergency release. |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/tmux/genie.tmux.conf`:
- Line 59: The tmux status-left command currently pipes genie --version into
head which masks the failure and prevents the "|| echo Genie" fallback from
running; update the status-left line (the shell command inside the #( ... ) used
for status-left) to remove the `| head -1` so the pipeline returns the genie
process exit code and the fallback `|| echo Genie` will execute when `genie
--version` fails. Ensure the remainder of the status-left formatting (the
surrounding #[...] tokens and colors) is left unchanged.
In `@src/lib/team-manager.ts`:
- Around line 176-185: The current branch-creation block in team-manager.ts
silently falls back to creating the new branch from HEAD when the provided
baseBranch can't be resolved (see variables branchExists, branchName,
baseBranch), which can create a branch from the wrong commit; change the logic
so that if both attempts to create from origin/${baseBranch} and ${baseBranch}
fail you surface an explicit error instead of falling back to `git branch
${branchName}` — e.g., capture the caught error(s) and throw or return a clear
error describing that baseBranch could not be found (include baseBranch and
repoPath in the message) so callers can handle it rather than silently creating
the branch from the current HEAD.
- Around line 167-168: The early return that skips cloning when
existsSync(worktreePath) is true is unsafe; instead, validate that worktreePath
points to a healthy git worktree (e.g., check for a .git directory or run a git
probe such as `git rev-parse --is-inside-work-tree`) and only return if that
validation passes; if validation fails, either clean up the stale path (remove
or rename) or surface an error so the subsequent team creation logic uses a
valid worktree; update the check around existsSync(worktreePath) to call a
helper like isValidWorktree(worktreePath) and branch accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9528bb65-1e5b-4cdf-b85d-34937725c22d
📒 Files selected for processing (5)
scripts/tmux/genie.tmux.confsrc/genie.tssrc/lib/team-manager.test.tssrc/lib/team-manager.tssrc/term-commands/ship.test.ts
| set -g status-left "#[bg=#7b2ff7,fg=#e0e0e0,bold] #(genie --version 2>/dev/null | head -1 || echo 'Genie') #[bg=#1a1a2e,fg=#7b2ff7]" | ||
| # Left: branding | ||
| set -g status-left-length 40 | ||
| set -g status-left "#[bg=#7b2ff7,fg=#e0e0e0,bold] 🧞 #(genie --version 2>/dev/null | head -1 || echo Genie) #[bg=#1a1a2e,fg=#7b2ff7]" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify current behavior: fallback is masked by pipeline status.
current="$(bash -lc 'missing_genie --version 2>/dev/null | head -1 || echo Genie')"
printf "current=[%s]\n" "$current"
# Verify fixed behavior: fallback appears when command is missing.
fixed="$(bash -lc 'missing_genie --version 2>/dev/null || echo Genie')"
printf "fixed=[%s]\n" "$fixed"Repository: automagik-dev/genie
Length of output: 302
Remove | head -1 to ensure fallback logic executes when genie is missing.
On line 59, the head command masks pipeline failure. When genie --version fails, head still exits successfully (exit 0), preventing the || echo Genie fallback from triggering. The result is blank status output instead of the "Genie" fallback.
Proposed fix
-set -g status-left "#[bg=#7b2ff7,fg=#e0e0e0,bold] 🧞 #(genie --version 2>/dev/null | head -1 || echo Genie) #[bg=#1a1a2e,fg=#7b2ff7]"
+set -g status-left "#[bg=#7b2ff7,fg=#e0e0e0,bold] 🧞 #(genie --version 2>/dev/null || echo Genie) #[bg=#1a1a2e,fg=#7b2ff7]"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| set -g status-left "#[bg=#7b2ff7,fg=#e0e0e0,bold] 🧞 #(genie --version 2>/dev/null | head -1 || echo Genie) #[bg=#1a1a2e,fg=#7b2ff7]" | |
| set -g status-left "#[bg=#7b2ff7,fg=#e0e0e0,bold] 🧞 #(genie --version 2>/dev/null || echo Genie) #[bg=#1a1a2e,fg=#7b2ff7]" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/tmux/genie.tmux.conf` at line 59, The tmux status-left command
currently pipes genie --version into head which masks the failure and prevents
the "|| echo Genie" fallback from running; update the status-left line (the
shell command inside the #( ... ) used for status-left) to remove the `| head
-1` so the pipeline returns the genie process exit code and the fallback `||
echo Genie` will execute when `genie --version` fails. Ensure the remainder of
the status-left formatting (the surrounding #[...] tokens and colors) is left
unchanged.
| // Skip if clone already exists on disk | ||
| if (existsSync(worktreePath)) return; |
There was a problem hiding this comment.
Validate existing target path before returning.
Line 167 returns on path existence alone. If that directory is stale/partial/non-git, team creation proceeds with an invalid worktreePath.
Proposed fix
- // Skip if clone already exists on disk
- if (existsSync(worktreePath)) return;
+ // Skip only if a valid git clone already exists on disk
+ if (existsSync(worktreePath)) {
+ try {
+ await $`git -C ${worktreePath} rev-parse --is-inside-work-tree`.quiet();
+ return;
+ } catch {
+ throw new Error(`Path exists but is not a valid git clone: ${worktreePath}`);
+ }
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/team-manager.ts` around lines 167 - 168, The early return that skips
cloning when existsSync(worktreePath) is true is unsafe; instead, validate that
worktreePath points to a healthy git worktree (e.g., check for a .git directory
or run a git probe such as `git rev-parse --is-inside-work-tree`) and only
return if that validation passes; if validation fails, either clean up the stale
path (remove or rename) or surface an error so the subsequent team creation
logic uses a valid worktree; update the check around existsSync(worktreePath) to
call a helper like isValidWorktree(worktreePath) and branch accordingly.
| // Branch doesn't exist yet — create it from baseBranch | ||
| if (!branchExists) { | ||
| try { | ||
| await $`git -C ${repoPath} branch ${branchName} origin/${baseBranch}`.quiet(); | ||
| } catch { | ||
| try { | ||
| await $`git -C ${repoPath} branch ${branchName} ${baseBranch}`.quiet(); | ||
| } catch { | ||
| await $`git -C ${repoPath} branch ${branchName}`.quiet(); | ||
| } |
There was a problem hiding this comment.
Do not silently ignore missing baseBranch.
Line 184 falls back to git branch ${branchName} (current HEAD) when baseBranch cannot be resolved. That can create the team branch from the wrong commit without surfacing an error.
Proposed fix
try {
await $`git -C ${repoPath} branch ${branchName} ${baseBranch}`.quiet();
} catch {
- await $`git -C ${repoPath} branch ${branchName}`.quiet();
+ throw new Error(
+ `Base branch "${baseBranch}" was not found locally or on origin in ${repoPath}`,
+ );
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/team-manager.ts` around lines 176 - 185, The current branch-creation
block in team-manager.ts silently falls back to creating the new branch from
HEAD when the provided baseBranch can't be resolved (see variables branchExists,
branchName, baseBranch), which can create a branch from the wrong commit; change
the logic so that if both attempts to create from origin/${baseBranch} and
${baseBranch} fail you surface an explicit error instead of falling back to `git
branch ${branchName}` — e.g., capture the caught error(s) and throw or return a
clear error describing that baseBranch could not be found (include baseBranch
and repoPath in the message) so callers can handle it rather than silently
creating the branch from the current HEAD.
…-border (#656) - Top: pane-border with system info (git, CPU, RAM, clock) - Bottom line 0: native clickable window tabs - Bottom line 1: clickable session list + Genie version (right) - Fix update-check to use @automagik/genie (scoped package) - Only show update notice when npm version is actually newer Co-authored-by: Genie <genie@automagik.ai>
Rolling Promotion PR
Auto-maintained rolling promotion PR from
devtomain.Process:
ready-to-mergeadded when all checks passSummary by CodeRabbit
Chores
New Features
Improvements
Tests