Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ in
./pi
./serena
./starship
./tmuxinator
./worktrunk
./zellij
]
Expand Down
4 changes: 4 additions & 0 deletions config/tmuxinator/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{ ... }:
{
xdg.configFile."tmuxinator".source = ./tmuxinator;
}
9 changes: 9 additions & 0 deletions config/tmuxinator/tmuxinator/desktop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: desktop
windows:
- btop: btop
- dotfiles:
layout: even-horizontal
root: ~/dotfiles
panes:
-
-
9 changes: 9 additions & 0 deletions config/tmuxinator/tmuxinator/mobile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: mobile
windows:
- btop: btop
- dotfiles:
layout: even-horizontal
root: ~/dotfiles
panes:
-
-
9 changes: 9 additions & 0 deletions config/tmuxinator/tmuxinator/primary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: primary
windows:
- btop: btop
- dotfiles:
layout: even-horizontal
root: ~/dotfiles
panes:
-
-
4 changes: 4 additions & 0 deletions config/tmuxinator/tmuxinator/work.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name: work
windows:
- editor: nvim
- shell:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to primary.yml, the shell: entry is redundant. Tmuxinator will open a shell by default if no command is specified for a window.

  - shell

1 change: 1 addition & 0 deletions home-manager/packages/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ with pkgs;
sqlite
stern
tealdeer
tmuxinator
tokei
tree
turso-cli
Expand Down
9 changes: 8 additions & 1 deletion home-manager/programs/fish/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@
j = "jj";
lzd = "lazydocker";
lzg = "lazygit";
ta = "tmux new -A -s default";
v = "nvim";

# Function-based abbreviations
Expand Down Expand Up @@ -119,6 +118,10 @@
tdo = "_tdo_function";
tmo = "_tmo_function";
tpo = "_tpo_function";
tsh = "_tsh_function";
tss = "_tss_function";
tsw = "_tsw_function";
two = "_two_function";
zdo = "_zdo_function";
zmo = "_zmo_function";
zpo = "_zpo_function";
Expand Down Expand Up @@ -207,6 +210,10 @@
"_tdo_function"
"_tmo_function"
"_tpo_function"
"_tsh_function"
"_tss_function"
"_tsw_function"
"_two_function"
"_zdo_function"
"_zmo_function"
"_zpo_function"
Expand Down
10 changes: 9 additions & 1 deletion home-manager/programs/fish/functions/_tdo_function.fish
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
function _tdo_function --description "Attach to tmux desktop session"
tmux new-session -A -s desktop
if tmux has-session -t desktop 2>/dev/null
if test -n "$TMUX"
tmux switch-client -t desktop
else
tmux attach-session -t desktop
end
else

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function doesn't verify that tmuxinator is available before calling it. If tmuxinator is not installed, the function will fail with an unclear error. Consider adding a check like 'if not command -q tmuxinator' and providing a helpful error message.

Suggested change
else
else
if not command -q tmuxinator
echo "tmuxinator is not installed or not in PATH. Please install tmuxinator to start the 'desktop' session." >&2
return 1
end

Copilot uses AI. Check for mistakes.
tmuxinator start desktop
end
Comment on lines +8 to +10

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "*_t*_function.fish" -o -name "*tdo*" -o -name "*tmo*" -o -name "*tpo*" | head -20

Repository: shunkakinoki/dotfiles

Length of output: 484


🏁 Script executed:

fd -t f "*function.fish" | grep -E "(tdo|tmo|tpo)" | head -20

Repository: shunkakinoki/dotfiles

Length of output: 358


🏁 Script executed:

find . -path "*/fish/functions*" -type f | head -20

Repository: shunkakinoki/dotfiles

Length of output: 1283


🏁 Script executed:

cat -n ./home-manager/programs/fish/functions/_tdo_function.fish

Repository: shunkakinoki/dotfiles

Length of output: 415


🏁 Script executed:

cat -n ./home-manager/programs/fish/functions/_tmo_function.fish

Repository: shunkakinoki/dotfiles

Length of output: 410


🏁 Script executed:

cat -n ./home-manager/programs/fish/functions/_tpo_function.fish

Repository: shunkakinoki/dotfiles

Length of output: 415


🌐 Web query:

tmuxinator issue 924 TMUX environment variable

💡 Result:

tmuxinator issue #924 was caused by tmuxinator generating the session creation command with an empty TMUX env var, e.g.:

Because TMUX contains the current tmux server’s socket path, unsetting it makes tmux new-session talk to (or start) a different server (typically the default socket). Then the rest of tmuxinator’s commands run against your original server and spam can't find session. (github.com)

Fix: PR #925 removed that TMUX= prefix and was released in tmuxinator v3.3.1 (“Don’t unset TMUX env variable for new-session”). (github.com)

What to do:

Citations:


🌐 Web query:

tmuxinator attach-session switch-client inside tmux

💡 Result:

When you’re already inside tmux, you generally want to switch the current client to another session (not “attach” a new client).

Use switch-client (recommended inside tmux)

tmux switch-client -t <session-name>

switch-client is explicitly meant to “switch the current session for a client”. [1]

What about attach-session inside tmux?

attach-session is primarily for outside tmux (“create a new client … and attach it”). When run inside tmux, it effectively switches the current client—but this is often confusing; the clearer equivalent command is switch-client. [1][2]

If this is from tmuxinator

tmuxinator can be configured to not auto-attach:

attach: false

Then you can start the project and switch from within tmux using tmux switch-client -t .... [3]

Sources:
[1] Unix & Linux Stack Exchange (explains attach-session vs switch-client inside tmux) (unix.stackexchange.com)
[2] tmux Wiki: Getting Started (client/session model) (github.com)
[3] tmuxinator README (attach: false) (github.com)

Citations:


After tmuxinator start, add switch-client to navigate to the new session when inside tmux.

The three functions consistently use switch-client when navigating to an existing session inside tmux (lines 3–4), but the new-session path (lines 8–10) lacks this call after tmuxinator start. Inside an active tmux session, switch-client is the correct command to move the current client to a different session; without it, the user remains in their original session with no visible change.

Proposed fix
   else
     tmuxinator start desktop
+    if test -n "$TMUX"
+      tmux switch-client -t desktop
+    end
   end

Apply the same fix to _tmo_function.fish (line 9) and _tpo_function.fish (line 9), replacing desktop with mobile and primary respectively.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@home-manager/programs/fish/functions/_tdo_function.fish` around lines 8 - 10,
After starting a new session with tmuxinator, call tmux's switch-client so an
attached client inside tmux moves to the new session: in _tdo_function.fish add
a call to switch-client immediately after tmuxinator start desktop; apply the
same change in _tmo_function.fish (after tmuxinator start mobile) and
_tpo_function.fish (after tmuxinator start primary) so the commands use
switch-client when executed from within an active tmux client.

end
10 changes: 9 additions & 1 deletion home-manager/programs/fish/functions/_tmo_function.fish
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
function _tmo_function --description "Attach to tmux mobile session"
tmux new-session -A -s mobile
if tmux has-session -t mobile 2>/dev/null
if test -n "$TMUX"
tmux switch-client -t mobile
else
tmux attach-session -t mobile
end
else
tmuxinator start mobile

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function doesn't verify that tmuxinator is available before calling it. If tmuxinator is not installed, the function will fail with an unclear error. Consider adding a check like 'if not command -q tmuxinator' and providing a helpful error message.

Suggested change
tmuxinator start mobile
if command -q tmuxinator
tmuxinator start mobile
else
echo "Error: 'tmuxinator' command not found. Please install tmuxinator to start the 'mobile' session." >&2
return 1
end

Copilot uses AI. Check for mistakes.
end
end
10 changes: 9 additions & 1 deletion home-manager/programs/fish/functions/_tpo_function.fish
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
function _tpo_function --description "Attach to tmux primary session"
tmux new-session -A -s primary
if tmux has-session -t primary 2>/dev/null
if test -n "$TMUX"
tmux switch-client -t primary
else
tmux attach-session -t primary
end
else

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function doesn't verify that tmuxinator is available before calling it. If tmuxinator is not installed, the function will fail with an unclear error. Consider adding a check like 'if not command -q tmuxinator' and providing a helpful error message.

Suggested change
else
else
if not command -q tmuxinator
echo "Error: 'tmuxinator' is not installed or not found in PATH. Cannot start primary session." >&2
return 1
end

Copilot uses AI. Check for mistakes.
tmuxinator start primary
end
end
79 changes: 79 additions & 0 deletions home-manager/programs/fish/functions/_tsh_function.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
function _tsh_function --description "Search tmux session history log or pane contents"
set -l log ~/.local/share/tmux/session-history.log
set -l pane_dir ~/.local/share/tmux/panes
set -l archive_dir ~/.local/share/tmux/archive

# With argument(s): search stored pane content files (live + archived)
if test (count $argv) -gt 0
if not test -d "$pane_dir"
echo "No pane content store found at $pane_dir"
return
end

set -l query (string join ' ' $argv)
set -l selected (rg -l -- "$query" "$pane_dir" "$archive_dir" 2>/dev/null \
| fzf --prompt="pane-search> " \
--height=40% \
--preview="rg -n -- '$query' {} 2>/dev/null | head -80" \

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --preview command passed to fzf embeds the unescaped query directly into a shell command string (rg -n -- '$query' {}), which allows a search term containing quotes or shell metacharacters to break out of the quoted context and inject arbitrary shell commands when the preview runs. Because pane contents often include text originating from external systems, copying such text into tsh as a search query can trigger this injection and execute attacker-controlled commands in your shell. To fix this, avoid interpolating query into a shell command string and instead pass it to rg as a properly escaped argument or use a preview mechanism that does not evaluate the query through a shell (e.g., by using safe argument passing or explicit escaping for user input).

Suggested change
--preview="rg -n -- '$query' {} 2>/dev/null | head -80" \
--preview="sh -c 'rg -n -- \"\$1\" \"\$2\" 2>/dev/null | head -80' sh {q} {}" \

Copilot uses AI. Check for mistakes.
--preview-window=right:60%)
Comment on lines +14 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preview command breaks on quotes and is command-injection prone.

$query is interpolated into a shell command inside single quotes. Queries containing ' can break preview, and crafted text can inject shell tokens.

Suggested fix (pass query via env var)
     set -l query (string join ' ' $argv)
+    set -lx TSH_QUERY $query
     set -l selected (rg -l -- "$query" "$pane_dir" "$archive_dir" 2>/dev/null \
       | fzf --prompt="pane-search> " \
             --height=40% \
-            --preview="rg -n -- '$query' {} 2>/dev/null | head -80" \
+            --preview='rg -n -- "$TSH_QUERY" {} 2>/dev/null | head -80' \
             --preview-window=right:60%)
📝 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.

Suggested change
set -l selected (rg -l -- "$query" "$pane_dir" "$archive_dir" 2>/dev/null \
| fzf --prompt="pane-search> " \
--height=40% \
--preview="rg -n -- '$query' {} 2>/dev/null | head -80" \
--preview-window=right:60%)
set -lx TSH_QUERY $query
set -l selected (rg -l -- "$query" "$pane_dir" "$archive_dir" 2>/dev/null \
| fzf --prompt="pane-search> " \
--height=40% \
--preview='rg -n -- "$TSH_QUERY" {} 2>/dev/null | head -80' \
--preview-window=right:60%)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@home-manager/programs/fish/functions/_tsh_function.fish` around lines 14 -
18, The preview command is vulnerable because $query is directly interpolated
into the --preview shell string; instead export/pass the query as an environment
variable to fzf and reference that safe var inside the preview command to avoid
quote-breaking and injection. Concretely, when constructing the pipeline that
assigns to the variable selected (the set -l selected ... | fzf invocation),
prefix the fzf invocation with an environment assignment like QUERY="$query" (or
use env QUERY="$query") and change the --preview value to use "$QUERY" (e.g.
--preview='rg -n -- "$QUERY" {} 2>/dev/null | head -80') so the user-provided
query is not shell-interpolated into the preview command.

Comment on lines +14 to +18

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function doesn't verify that required commands (rg and fzf) are available before using them. If either command is missing, the function will fail with unclear error messages. Consider adding checks like 'if not command -q rg' and providing helpful error messages to guide users to install missing dependencies.

Copilot uses AI. Check for mistakes.

if test -z "$selected"
return
end

# work--0--0.txt or work--0--0--20260226-103000.txt → sess=work, widx=0
set -l fname (string replace -r '.*/' '' "$selected" \
| string replace -r '--\d{8}-\d{6}\.txt$' '' \
| string replace '.txt' '')
set -l parts (string split -- '--' $fname)
set -l sess $parts[1]
set -l widx $parts[2]

Comment on lines +24 to +31

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parsing logic assumes filename format 'sess--widx--pidx.txt' or 'sess--widx--pidx--timestamp.txt', but doesn't handle edge cases where session names might contain '--'. For example, a session named 'my--project' with window 0 and pane 0 would create 'my--project--0--0.txt', and parsing this by splitting on '--' would incorrectly extract 'my' as the session name instead of 'my--project'. Consider using a more robust delimiter or parsing strategy.

Suggested change
# work--0--0.txt or work--0--0--20260226-103000.txt → sess=work, widx=0
set -l fname (string replace -r '.*/' '' "$selected" \
| string replace -r '--\d{8}-\d{6}\.txt$' '' \
| string replace '.txt' '')
set -l parts (string split -- '--' $fname)
set -l sess $parts[1]
set -l widx $parts[2]
# sess may contain `--`: e.g. my--project--0--0.txt or my--project--0--0--20260226-103000.txt
set -l fname (string replace -r '.*/' '' "$selected" \
| string replace -r '--\d{8}-\d{6}\.txt$' '' \
| string replace '.txt' '')
# From right: [..., sess parts..., widx, pidx]
set -l parts (string split -- '--' $fname)
if test (count $parts) -lt 3
echo "Unrecognized pane filename format: $fname"
return
end
set -l widx $parts[-2]
set -l sess (string join -- '--' $parts[1..-3])

Copilot uses AI. Check for mistakes.
if not tmux has-session -t "$sess" 2>/dev/null
echo "Session '$sess' no longer exists (archived pane — content shown above in preview)"
return
end

if test -n "$TMUX"
tmux switch-client -t "$sess"
tmux select-window -t "$sess:$widx" 2>/dev/null
else
tmux attach-session -t "$sess" \; select-window -t "$sess:$widx"
end
return
Comment on lines +24 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Archived matches are treated as live in some cases.

Current logic only checks whether the session exists. If an archived file matches and that session still exists, the function jumps to a live window instead of returning the graceful archived message.

Suggested fix (explicit archived-path check)
     if test -z "$selected"
       return
     end
+
+    if string match -q "$archive_dir/*" "$selected"
+      echo "Archived pane selected — content available in preview only"
+      return
+    end
 
     # work--0--0.txt or work--0--0--20260226-103000.txt → sess=work, widx=0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@home-manager/programs/fish/functions/_tsh_function.fish` around lines 24 -
43, The function currently strips archived timestamps from $selected and then
checks tmux session existence, causing archived files with matching live
sessions to be treated as live; detect archived files first by testing $selected
for the timestamp pattern (e.g. use string match -r '\-\-\d{8}-\d{6}\.txt$' on
$selected) and if it matches, print the archived message and return (use the
same message that currently appears when a session no longer exists), otherwise
proceed to extract fname/parts and perform the tmux checks and attach/switch
logic; update references to $selected, $fname, $parts, $sess, and $widx in the
existing _tsh_function.fish code.

end

# No argument: fzf over the history log (session/window/path metadata)
if not test -f "$log"
echo "No session history found at $log"
return
end

set -l selected (cat "$log" | fzf \

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using 'cat' to pipe to fzf is unnecessary. You can pass the file directly to fzf using input redirection for better performance and fewer processes. Replace 'cat "$log" | fzf' with 'fzf < "$log"'.

Suggested change
set -l selected (cat "$log" | fzf \
set -l selected (fzf < "$log" \

Copilot uses AI. Check for mistakes.
--prompt="session-history> " \
--height=40% \
--tac \
--no-sort \
--preview='echo {}')

if test -z "$selected"
return
end

set -l target (string split ' ' $selected)[2]
set -l parts (string split ':' $target)
set -l sess $parts[1]
set -l widx $parts[2]

if not tmux has-session -t "$sess" 2>/dev/null
echo "Session '$sess' no longer exists"
return
end

if test -n "$TMUX"
tmux switch-client -t "$sess"
tmux select-window -t "$sess:$widx" 2>/dev/null
else
tmux attach-session -t "$sess" \; select-window -t "$sess:$widx"
end
end
52 changes: 52 additions & 0 deletions home-manager/programs/fish/functions/_tss_function.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
function _tss_function --description "Fuzzy-pick or create a tmux session"
set -l default_sessions primary mobile desktop work

# Build candidate list: default sessions first, then any extra existing sessions
set -l existing (tmux list-sessions -F '#S' 2>/dev/null)
set -l candidates $default_sessions
for s in $existing
if not contains $s $default_sessions
set -a candidates $s
end
end

set -l selected (printf '%s\n' $candidates | fzf \
--prompt="session> " \
--height=40% \
--preview='tmux list-windows -F "#I: #W" -t {} 2>/dev/null' \
--bind='ctrl-x:execute-silent(tmux kill-session -t {})+abort')
Comment on lines +13 to +17

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function doesn't verify that fzf or tmuxinator are available before using them. If either is not installed, the function will fail with unclear errors. Consider adding checks like 'if not command -q fzf' and 'if not command -q tmuxinator' with helpful error messages.

Copilot uses AI. Check for mistakes.

if test -n "$selected"
if tmux has-session -t "$selected" 2>/dev/null
if test -n "$TMUX"
tmux switch-client -t "$selected"
else
tmux attach-session -t "$selected"
end
else if test "$selected" = work

@cubic-dev-ai cubic-dev-ai Bot Feb 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The 'work' session handling logic here (resurrect script lookup, session check, switch/attach, tmuxinator fallback) is duplicated from _two_function. If either copy is updated independently, they'll diverge. Consider calling _two_function directly when $selected = work to centralize this logic.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/programs/fish/functions/_tss_function.fish, line 26:

<comment>The 'work' session handling logic here (resurrect script lookup, session check, switch/attach, tmuxinator fallback) is duplicated from `_two_function`. If either copy is updated independently, they'll diverge. Consider calling `_two_function` directly when `$selected = work` to centralize this logic.</comment>

<file context>
@@ -0,0 +1,52 @@
+      else
+        tmux attach-session -t "$selected"
+      end
+    else if test "$selected" = work
+      set -l restore (tmux list-keys 2>/dev/null | string match -rg '(/\S+/resurrect/scripts/restore\.sh)')
+      set -l restore $restore[1]
</file context>
Fix with Cubic

set -l restore (tmux list-keys 2>/dev/null | string match -rg '(/\S+/resurrect/scripts/restore\.sh)')
set -l restore $restore[1]
if test -n "$restore"
tmux run-shell "$restore"
end
Comment on lines +29 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

tmux run-shell silently fails when no tmux server is running.

If the user invokes tss from outside tmux with no active sessions, tmux run-shell "$restore" fails (no server), the restore is skipped without any feedback, and the function silently falls through to tmuxinator start work. Consider adding a server-presence guard or at minimum an error message:

🛡️ Proposed fix — guard or surface the failure
-      if test -n "$restore"
-        tmux run-shell "$restore"
-      end
+      if test -n "$restore"
+        if tmux list-sessions &>/dev/null
+          tmux run-shell "$restore"
+        else
+          echo "tmux server not running; skipping resurrect restore" >&2
+        end
+      end
📝 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.

Suggested change
if test -n "$restore"
tmux run-shell "$restore"
end
if test -n "$restore"
if tmux list-sessions &>/dev/null
tmux run-shell "$restore"
else
echo "tmux server not running; skipping resurrect restore" >&2
end
end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@home-manager/programs/fish/functions/_tss_function.fish` around lines 29 -
31, In _tss_function.fish, before calling tmux run-shell with the $restore
payload, guard against no tmux server by checking tmux list-sessions (or tmux
ls) and only call tmux run-shell when that check succeeds; if it fails, print a
concise error/warning to stderr (e.g. "No tmux server/sessions; skipping
restore") and skip the run-shell (or return a non-zero status if you want to
abort), so replace the unconditional tmux run-shell "$restore" with a
conditional that tests tmux list-sessions' exit code and surfaces the failure.

if tmux has-session -t work 2>/dev/null
if test -n "$TMUX"
tmux switch-client -t work
else
tmux attach-session -t work
end
else
tmuxinator start work
end
Comment on lines +26 to +40

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for handling the 'work' session, including the resurrect script and subsequent attachment/start, is duplicated here from _two_function. To improve maintainability and avoid code repetition, consider calling _two_function directly when test "$selected" = work is true. This would centralize the logic for the 'work' session.

    else if test "$selected" = work
      _two_function

Comment on lines +26 to +40

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for restoring the work session using tmux-resurrect and creating it with tmuxinator is duplicated between _two_function (lines 2-6, 8-15) and _tss_function (lines 26-40). Consider extracting this into a shared helper function to reduce code duplication and ensure consistent behavior.

Copilot uses AI. Check for mistakes.
else if contains $selected $default_sessions
tmuxinator start "$selected"
else
tmux new-session -d -s "$selected"
if test -n "$TMUX"
tmux switch-client -t "$selected"
else
tmux attach-session -t "$selected"
end
end
end
end
21 changes: 21 additions & 0 deletions home-manager/programs/fish/functions/_tsw_function.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
function _tsw_function --description "Fuzzy-pick any window across all sessions"

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function doesn't verify that fzf is available before using it. If fzf is not installed, the function will fail with an unclear error. Consider adding a check like 'if not command -q fzf' and providing a helpful error message.

Suggested change
function _tsw_function --description "Fuzzy-pick any window across all sessions"
function _tsw_function --description "Fuzzy-pick any window across all sessions"
if not command -q fzf
echo "Error: fzf is required by _tsw_function but is not installed or not in PATH." >&2
return 1
end

Copilot uses AI. Check for mistakes.
set -l selected (tmux list-windows -a -F '#{session_name}:#{window_index} #{window_name}' 2>/dev/null \
| fzf --prompt="window> " --height=40% \
--preview='set t (string split " " {})[1]; tmux list-panes -t $t -F "#P: #{pane_current_command} #{pane_current_path}" 2>/dev/null')

@cubic-dev-ai cubic-dev-ai Bot Feb 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The fzf preview uses fish-only syntax but fzf executes previews via $SHELL -c (or sh -c). If SHELL isn’t fish (common when fish is launched from another login shell), the preview command fails and the preview pane won’t render. Consider forcing fish for the preview command or using POSIX-compatible syntax.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/programs/fish/functions/_tsw_function.fish, line 4:

<comment>The fzf preview uses fish-only syntax but fzf executes previews via `$SHELL -c` (or `sh -c`). If `SHELL` isn’t fish (common when fish is launched from another login shell), the preview command fails and the preview pane won’t render. Consider forcing fish for the preview command or using POSIX-compatible syntax.</comment>

<file context>
@@ -0,0 +1,21 @@
+function _tsw_function --description "Fuzzy-pick any window across all sessions"
+  set -l selected (tmux list-windows -a -F '#{session_name}:#{window_index}  #{window_name}' 2>/dev/null \
+    | fzf --prompt="window> " --height=40% \
+          --preview='set t (string split "  " {})[1]; tmux list-panes -t $t -F "#P: #{pane_current_command}  #{pane_current_path}" 2>/dev/null')
+
+  if test -z "$selected"
</file context>
Fix with Cubic


if test -z "$selected"
return
end

set -l target (string split ' ' $selected)[1] # "session:index"
set -l parts (string split ':' $target)
set -l sess $parts[1]
set -l widx $parts[2]

if test -n "$TMUX"
tmux switch-client -t "$sess"
tmux select-window -t "$sess:$widx"
else
tmux attach-session -t "$sess" \; select-window -t "$sess:$widx"
end
end
17 changes: 17 additions & 0 deletions home-manager/programs/fish/functions/_two_function.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function _two_function --description "Attach to tmux work session"
set -l restore (tmux list-keys 2>/dev/null | string match -rg '(/\S+/resurrect/scripts/restore\.sh)')
set -l restore $restore[1]
if test -n "$restore"
tmux run-shell "$restore"
end

if tmux has-session -t work 2>/dev/null
if test -n "$TMUX"
tmux switch-client -t work
else
tmux attach-session -t work
end
else
tmuxinator start work

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function doesn't verify that tmuxinator is available before calling it. If tmuxinator is not installed or not in PATH, the function will fail with an unclear error. Consider adding a check like 'if not command -q tmuxinator' and providing a helpful error message.

Suggested change
tmuxinator start work
if command -q tmuxinator
tmuxinator start work
else
echo "tmuxinator is not installed or not in PATH; cannot start 'work' session" >&2
return 1
end

Copilot uses AI. Check for mistakes.
end
end
5 changes: 5 additions & 0 deletions home-manager/programs/tmux/default.nix
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{ pkgs, ... }:
{
home.file.".config/tmux/session-logger.sh" = {
executable = true;
source = ./session-logger.sh;
};

programs.tmux = {
enable = true;
extraConfig = builtins.readFile ./tmux.conf;
Expand Down
44 changes: 44 additions & 0 deletions home-manager/programs/tmux/session-logger.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env sh
mkdir -p ~/.local/share/tmux/panes
mkdir -p ~/.local/share/tmux/archive
LOG=~/.local/share/tmux/session-history.log

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The session-history.log file grows unbounded and is never rotated or pruned. Over time, this could consume significant disk space. Consider implementing log rotation (e.g., keeping only the last N days or last N MB) or using a tool like logrotate to manage this file.

Copilot uses AI. Check for mistakes.
PANE_DIR=~/.local/share/tmux/panes
ARCHIVE_DIR=~/.local/share/tmux/archive

while true; do
sleep 30
Comment on lines +1 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing single-instance guard can cause logger races.

If multiple logger processes run concurrently, they can race on .txt/.old rotation and archive moves, causing data loss or duplicate churn.

Suggested fix (lock directory)
 #!/usr/bin/env sh
+LOCK_DIR="${XDG_RUNTIME_DIR:-/tmp}/tmux-session-logger.lock"
+if ! mkdir "$LOCK_DIR" 2>/dev/null; then
+  exit 0
+fi
+trap 'rmdir "$LOCK_DIR"' EXIT INT TERM
+
 mkdir -p ~/.local/share/tmux/panes
 mkdir -p ~/.local/share/tmux/archive

Also applies to: 35-43

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@home-manager/programs/tmux/session-logger.sh` around lines 1 - 9, Add a
single-instance lock using an atomic lock-directory approach to prevent
concurrent logger processes from racing on rotation/archive; at script start try
to create a lock dir (e.g., /tmp/tmux-session-logger.lock), if it already exists
check the PID inside and whether that PID is alive and exit if active, otherwise
remove stale lock and acquire it; write current PID into the lock dir and
register a trap to remove the lock dir on EXIT/INT/TERM so cleanup always
happens; apply this guard around the main loop and before any rotation/archive
operations that use LOG, PANE_DIR, and ARCHIVE_DIR (including the rotation/move
logic referenced in the later rotation/archive block) so only one process
performs .txt/.old rotation and archive moves at a time.


# Append window metadata
tmux list-windows -a \
-F "$(date +%Y-%m-%dT%H:%M:%S) #{session_name}:#{window_index} #{window_name} #{pane_current_path}" \
>>"$LOG" 2>/dev/null

# Rotate existing live snapshots to .old
for f in "$PANE_DIR"/*.txt; do

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The glob pattern '.txt' on line 17 will expand to the literal string '.txt' if no .txt files exist in PANE_DIR during the first run. The '[ -f "$f" ]' check handles this, but the loop will still execute once with the literal pattern. Consider adding 'set -f' before the loop or using 'nullglob' behavior to skip the loop entirely when no files match. Alternatively, add '|| true' after line 19 to ensure the script doesn't exit if 'mv' fails.

Suggested change
for f in "$PANE_DIR"/*.txt; do
set -- "$PANE_DIR"/*.txt
[ -e "$1" ] || set --
for f in "$@"; do

Copilot uses AI. Check for mistakes.
[ -f "$f" ] && mv "$f" "${f%.txt}.old"
done

# Recapture all currently live panes
tmux list-panes -a -F "#{session_name} #{window_index} #{pane_index} #{pane_id}" \
2>/dev/null | while IFS= read -r line; do
sess=$(printf '%s' "$line" | cut -d' ' -f1)
widx=$(printf '%s' "$line" | cut -d' ' -f2)
pidx=$(printf '%s' "$line" | cut -d' ' -f3)
pane_id=$(printf '%s' "$line" | cut -d' ' -f4)
tmux capture-pane -pt "$pane_id" -S - 2>/dev/null \
>"$PANE_DIR/$sess--$widx--$pidx.txt"
done

# For each .old: if a live .txt exists → pane survived → delete .old
# if no live .txt → pane closed → archive with timestamp
ts=$(date +%Y%m%d-%H%M%S)
for old in "$PANE_DIR"/*.old; do
[ -f "$old" ] || continue
base=$(basename "${old%.old}")

@cubic-dev-ai cubic-dev-ai Bot Feb 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Archived pane filenames include a duplicated .txt suffix because .txt is kept in base and then .txt is appended again. Strip .txt when computing base so archived files match the intended naming scheme.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/programs/tmux/session-logger.sh, line 37:

<comment>Archived pane filenames include a duplicated `.txt` suffix because `.txt` is kept in `base` and then `.txt` is appended again. Strip `.txt` when computing `base` so archived files match the intended naming scheme.</comment>

<file context>
@@ -0,0 +1,44 @@
+  ts=$(date +%Y%m%d-%H%M%S)
+  for old in "$PANE_DIR"/*.old; do
+    [ -f "$old" ] || continue
+    base=$(basename "${old%.old}")
+    if [ -f "$PANE_DIR/$base.txt" ]; then
+      rm -f "$old"
</file context>
Fix with Cubic

if [ -f "$PANE_DIR/$base.txt" ]; then
rm -f "$old"
else
mv "$old" "$ARCHIVE_DIR/${base}--${ts}.txt"
Comment on lines +34 to +41

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The timestamp is generated once per cycle (line 34) and used for all archived panes in that cycle. If multiple panes close in the same 30-second window, they will all have identical timestamps in their archive filenames, which could cause filename collisions and data loss. Consider adding a unique identifier (like the pane index) to the timestamp or generating a fresh timestamp for each archived file.

Copilot uses AI. Check for mistakes.
fi
Comment on lines +22 to +42

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Pane index reuse can silently drop closed-pane archives.

Using sess--widx--pidx as the unique key is not stable. If a pane closes and a new pane reuses the same index before the next cycle, the old snapshot is treated as “survived” and deleted instead of archived.

Suggested fix (use `pane_id` in filenames)
-  tmux list-panes -a -F "#{session_name} #{window_index} #{pane_index} #{pane_id}" \
+  tmux list-panes -a -F "#{session_name} #{window_index} #{pane_index} #{pane_id}" \
     2>/dev/null | while IFS= read -r line; do
@@
-      tmux capture-pane -pt "$pane_id" -S - 2>/dev/null \
-        > "$PANE_DIR/$sess--$widx--$pidx.txt"
+      tmux capture-pane -pt "$pane_id" -S - 2>/dev/null \
+        > "$PANE_DIR/$sess--$widx--$pidx--$pane_id.txt"
     done
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@home-manager/programs/tmux/session-logger.sh` around lines 22 - 42, The
current snapshot filenames use sess--widx--pidx which is not stable; change the
snapshot creation in the tmux capture step to include the unique pane_id (e.g.
write to "$PANE_DIR/$sess--$widx--$pidx--$pane_id.txt" instead of
"$PANE_DIR/$sess--$widx--$pidx.txt") and update the .old handling loop to
compare/move using that same pane_id-aware basename (keep using
base="${old%.old}" but ensure live check uses "$PANE_DIR/$base.txt" where base
now contains the pane_id component); in short, modify the tmux capture target
and the logic in the for-old loop so filenames and comparisons incorporate
pane_id (references: variables sess, widx, pidx, pane_id, PANE_DIR, ARCHIVE_DIR,
old, base).

done
done
Comment on lines +1 to +44

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The session-logger.sh script lacks test coverage. Given that this repository has comprehensive test coverage for other shell scripts (as seen in spec/ directory), consider adding tests for session-logger.sh to verify: 1) correct filename generation, 2) archival logic when panes close, 3) handling of edge cases like missing directories or tmux not running, and 4) proper cleanup of .old files.

Copilot uses AI. Check for mistakes.
14 changes: 14 additions & 0 deletions home-manager/programs/tmux/tmux.conf
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,19 @@ set -g @thumbs-key Space
# Sessionx (session manager)
set -g @sessionx-bind 'o'

# Tmux sessionizer (fzf session picker)
bind S run-shell "tmux new-window 'fish -c _tss_function'"

# Last session (quick flip)
bind Tab switch-client -l

# Cross-session window picker
bind W run-shell "tmux new-window 'fish -c _tsw_function'"

# Extrakto (text extraction)
set -g @extrakto_key 'tab'

set -g history-limit 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

tmux history-limit 0 behavior unlimited scrollback

💡 Result:

In tmux, history-limit is a maximum number of lines kept per pane. So:

  • set -g history-limit 0 means keep 0 lines of history → effectively no tmux scrollback (copy/scroll mode won’t have anything older to show). (src.rivoreo.one)
  • tmux doesn’t have a true “unlimited” scrollback setting; the practical approach is to set a very large history-limit (at the cost of more memory). (src.rivoreo.one)
  • The setting only affects new windows/panes; existing ones keep the limit they were created with, so you may need to create a new window or restart the tmux server to see the change. (ss64.com)

Example “effectively unlimited” config:

set -g history-limit 200000

Citations:


history-limit 0 disables history; use a large positive value instead.

In tmux, history-limit 0 configures the pane to retain zero lines of history, which effectively disables scrollback and breaks capture-pane -S -. Set a large value like 200000 for practical unlimited history.

Suggested change
-set -g history-limit 0
+set -g history-limit 200000
📝 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.

Suggested change
set -g history-limit 0
set -g history-limit 200000
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@home-manager/programs/tmux/tmux.conf` at line 151, Replace the tmux
history-limit setting that currently disables scrollback: locate the line with
"set -g history-limit 0" in the tmux configuration and change it to a large
positive value (e.g., set -g history-limit 200000) so capture-pane and scrolling
work correctly; ensure you update any conditional or duplicate occurrences of
history-limit to the same positive value.

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting history-limit to 0 (unlimited) can lead to unbounded memory growth in long-running tmux sessions with heavy output. This is especially risky for processes that produce continuous output (logs, build processes, etc.). Consider setting a high but bounded limit (e.g., 500000 lines) instead, which still provides extensive history while preventing memory exhaustion.

Suggested change
set -g history-limit 0
set -g history-limit 500000

Copilot uses AI. Check for mistakes.

# Persistent session history logger (starts once per server boot)
set-hook -g 'server-startup' 'run-shell -b "~/.config/tmux/session-logger.sh"'
1 change: 1 addition & 0 deletions spec/coverage_spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ home-manager/modules/uv-globals/install-uv-globals.sh
home-manager/modules/yek/install-yek.sh
home-manager/modules/yek/yek.sh
home-manager/programs/neovim/run_tests.sh
home-manager/programs/tmux/session-logger.sh
home-manager/services/brew-upgrader/upgrade.sh
home-manager/services/cliproxyapi/scripts/backup.sh
home-manager/services/cliproxyapi/scripts/hydrate.sh
Expand Down
Loading