Skip to content
Merged
74 changes: 61 additions & 13 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ lua-check-hammerspoon-dev: ## Run the Hammerspoon Lua check inside the Nix dev s
##@ Launchd Services

.PHONY: launchctl
launchctl: launchctl-brew-upgrader launchctl-openclaw launchctl-cliproxyapi launchctl-cliproxyapi-backup launchctl-code-syncer launchctl-docker-postgres launchctl-dotfiles-updater launchctl-neverssl-keepalive launchctl-ollama ## Restart all launchd agents.
launchctl: launchctl-brew-upgrader launchctl-openclaw launchctl-cliproxyapi launchctl-cliproxyapi-backup launchctl-code-syncer launchctl-docker-postgres launchctl-dotfiles-updater launchctl-neverssl-keepalive launchctl-ollama launchctl-tmux-session-logger ## Restart all launchd agents.

.PHONY: launchctl-brew-upgrader
launchctl-brew-upgrader: ## Restart brew-upgrader launchd agent.
Expand Down Expand Up @@ -776,17 +776,31 @@ launchctl-ollama: ## Restart ollama launchd agent.
@launchctl load ~/Library/LaunchAgents/org.nix-community.home.ollama.plist
@echo "✅ ollama restarted"

.PHONY: launchctl-tmux-session-logger
launchctl-tmux-session-logger: ## Restart tmux-session-logger launchd agent.
@echo "🔄 Restarting tmux-session-logger..."
@launchctl unload ~/Library/LaunchAgents/org.nix-community.home.tmux-session-logger.plist 2>/dev/null || true
@sleep 3
@launchctl load ~/Library/LaunchAgents/org.nix-community.home.tmux-session-logger.plist
@echo "✅ tmux-session-logger restarted"

##@ Systemd Services (Linux)

.PHONY: systemctl
systemctl: systemctl-cliproxyapi systemctl-code-syncer systemctl-docker-postgres systemctl-dotfiles-updater systemctl-ollama systemctl-openclaw ## Restart all systemd user services.
systemctl: systemctl-cliproxyapi systemctl-cliproxyapi-backup systemctl-code-syncer systemctl-docker-postgres systemctl-dotfiles-updater systemctl-make-updater systemctl-neverssl-keepalive systemctl-ollama systemctl-openclaw systemctl-tmux-session-logger ## Restart all systemd user services.

.PHONY: systemctl-cliproxyapi
systemctl-cliproxyapi: ## Reload systemd units for cliproxyapi (home-manager handles restart).
@echo "🔄 Reloading cliproxyapi..."
@systemctl --user daemon-reload
@echo "✅ cliproxyapi reloaded"

.PHONY: systemctl-cliproxyapi-backup
systemctl-cliproxyapi-backup: ## Restart cliproxyapi-backup systemd user service.
@echo "🔄 Restarting cliproxyapi-backup..."
@systemctl --user restart cliproxyapi-backup.service || true
@echo "✅ cliproxyapi-backup restarted"

.PHONY: systemctl-code-syncer
systemctl-code-syncer: ## Restart code-syncer systemd user service.
@echo "🔄 Restarting code-syncer..."
Expand All @@ -805,6 +819,18 @@ systemctl-dotfiles-updater: ## Restart dotfiles-updater systemd user service.
@systemctl --user restart dotfiles-updater.service || true
@echo "✅ dotfiles-updater restarted"

.PHONY: systemctl-make-updater
systemctl-make-updater: ## Restart make-updater systemd timer and service.
@echo "🔄 Restarting make-updater..."
@systemctl --user restart make-updater.timer || true
@echo "✅ make-updater restarted"

.PHONY: systemctl-neverssl-keepalive
systemctl-neverssl-keepalive: ## Restart neverssl-keepalive systemd timer and service.
@echo "🔄 Restarting neverssl-keepalive..."
@systemctl --user restart neverssl-keepalive.timer || true
@echo "✅ neverssl-keepalive restarted"

.PHONY: systemctl-ollama
systemctl-ollama: ## Restart ollama systemd user service.
@echo "🔄 Restarting ollama..."
Expand All @@ -822,6 +848,12 @@ systemctl-openclaw: ## Restart OpenClaw gateway systemd user service.
@echo "✅ openclaw restarted"


.PHONY: systemctl-tmux-session-logger
systemctl-tmux-session-logger: ## Restart tmux-session-logger systemd timer and service.
@echo "🔄 Restarting tmux-session-logger..."
@systemctl --user restart tmux-session-logger.timer || true
@echo "✅ tmux-session-logger restarted"

.PHONY: git-submodule-sync
git-submodule-sync: ## Sync and update git submodules.
@echo "🔁 Syncing and updating git submodules..."
Expand All @@ -845,18 +877,34 @@ shell-test-dev: ## Run shell tests inside the Nix dev shell (mirrors CI).
.PHONY: fish-test
fish-test: ## Run fish function tests using fishtape.
@echo "🐟 Running fish function tests..."
@if ! command -v fishtape >/dev/null 2>&1; then \
echo " fishtape not found, running inside Nix dev shell..."; \
$(MAKE) fish-test-dev; \
else \
fish_errors=$$(mktemp); \
fishtape spec/fish/*_test.fish 2>"$$fish_errors"; \
rc=$$?; \
if [ -s "$$fish_errors" ]; then \
echo "fish test stderr (failing):"; cat "$$fish_errors"; rm -f "$$fish_errors"; exit 1; \
@fish_runner=$$(command -v fishtape 2>/dev/null || true); \
if [ -z "$$fish_runner" ]; then \
set -- /nix/store/*-fishtape/bin/fishtape; \
if [ -x "$$1" ]; then \
fish_runner=$$1; \
echo " fishtape not on PATH, using $$fish_runner"; \
else \
echo " fishtape not found, running inside Nix dev shell..."; \
$(MAKE) fish-test-dev; \
exit $$?; \
Comment on lines +881 to +889

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

Prevent recursive fallback when fishtape is missing.

If fishtape is unavailable both locally and in the dev shell, Line 856 calls fish-test-dev, and Lines 877-880 immediately re-enter fish-test again. That turns a missing dependency into recursive self-invocation instead of a single clear failure. Add a one-shot guard, or have fish-test-dev invoke a lower-level runner target directly.

🛠️ Example guard
  else \
-    echo "  fishtape not found, running inside Nix dev shell..."; \
-    $(MAKE) fish-test-dev; \
+    if [ "$$FISHTAPE_BOOTSTRAPPED" = "1" ]; then \
+      echo "  fishtape not found in the Nix dev shell"; \
+      exit 1; \
+    fi; \
+    echo "  fishtape not found, running inside Nix dev shell..."; \
+    FISHTAPE_BOOTSTRAPPED=1 $(MAKE) fish-test-dev; \
     exit $$?; \
  fi; \
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` around lines 849 - 857, The current fallback in the fish-test
target can recursively re-invoke fish-test if fishtape is missing; change the
logic so either (A) add a one-shot guard environment variable (e.g.
FISH_TEST_DEV_INVOKED) checked in the fish-test target before calling make
fish-test-dev and set it when invoking fish-test-dev so the second invocation
aborts with a clear error instead of re-entering, or (B) modify fish-test-dev to
call a lower-level runner target (e.g. fish-test-runner) that performs the
dev-shell setup and executes fishtape directly without calling back into
fish-test; update references to fish_runner and the fish-test / fish-test-dev
invocation accordingly.

fi; \
rm -f "$$fish_errors"; exit $$rc; \
fi
fi; \
fish_home=$$(mktemp -d "$${TMPDIR:-/tmp}/fish-test.XXXXXX"); \
fish_errors=$$(mktemp); \
fish_errors_filtered=$$(mktemp); \
trap 'rm -rf "$$fish_home" "$$fish_errors" "$$fish_errors_filtered"' EXIT; \
mkdir -p "$$fish_home/.config/fish" "$$fish_home/.local/state" "$$fish_home/.local/share"; \
HOME="$$fish_home" \
XDG_CONFIG_HOME="$$fish_home/.config" \
XDG_STATE_HOME="$$fish_home/.local/state" \
XDG_DATA_HOME="$$fish_home/.local/share" \
"$$fish_runner" spec/fish/*_test.fish 2>"$$fish_errors"; \
rc=$$?; \
grep -v -E '^(warning: notify_register_file_descriptor\(\) failed with status 9\.|warning: Universal variable notifications may not be received\.)$$' "$$fish_errors" >"$$fish_errors_filtered" || true; \
if [ -s "$$fish_errors_filtered" ]; then \
echo "fish test stderr (failing):"; cat "$$fish_errors_filtered"; exit 1; \
fi; \
exit $$rc

.PHONY: fish-test-dev
fish-test-dev: ## Run fish tests inside the Nix dev shell (mirrors CI).
Expand Down
56 changes: 26 additions & 30 deletions home-manager/programs/tmux/session-logger.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,36 @@ LOG=~/.local/share/tmux/session-history.log
PANE_DIR=~/.local/share/tmux/panes
ARCHIVE_DIR=~/.local/share/tmux/archive

while true; do
sleep 30
timestamp=$(date +%Y-%m-%dT%H:%M:%S)

# 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
# Append window metadata
tmux list-windows -a \
-F "${timestamp} #{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
[ -f "$f" ] && mv "$f" "${f%.txt}.old"
done
# Rotate existing live snapshots to .old
for f in "$PANE_DIR"/*.txt; do
[ -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)
# 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 sess widx pidx pane_id; do
if [ -n "$sess" ]; then
Comment on lines +20 to +23
tmux capture-pane -pt "$pane_id" -S - 2>/dev/null \
>"$PANE_DIR/$sess--$widx--$pidx.txt"
done
fi
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}")
if [ -f "$PANE_DIR/$base.txt" ]; then
rm -f "$old"
else
mv "$old" "$ARCHIVE_DIR/${base}--${ts}.txt"
fi
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}")
if [ -f "$PANE_DIR/$base.txt" ]; then
rm -f "$old"
else
mv "$old" "$ARCHIVE_DIR/${base}--${ts}.txt"
fi
done
3 changes: 1 addition & 2 deletions home-manager/programs/tmux/tmux.conf
Original file line number Diff line number Diff line change
Expand Up @@ -157,5 +157,4 @@ set -g @extrakto_key 'tab'

set -g history-limit 2147483647

# Persistent session history logger (starts once per server boot)
run-shell "pgrep -f 'session-logger.sh' > /dev/null 2>&1 || (nohup ~/.config/tmux/session-logger.sh > /dev/null 2>&1 &)"
# Persistent session history logger is managed by launchd/systemd.
2 changes: 2 additions & 0 deletions home-manager/services/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ let
sshAgent = import ./ssh-agent {
inherit config lib pkgs;
};
tmuxSessionLogger = import ./tmux-session-logger { inherit pkgs; };
in
[
brewUpgrader
Expand All @@ -29,4 +30,5 @@ in
neversslKeepalive
ollama
sshAgent
tmuxSessionLogger
]
54 changes: 54 additions & 0 deletions home-manager/services/tmux-session-logger/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{ pkgs, ... }:
let
inherit (pkgs) lib;
sessionLoggerScript = ../../programs/tmux/session-logger.sh;

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there's an existing mechanism that installs session-logger.sh to a known location
rg -l 'session-logger' --type nix

Repository: shunkakinoki/dotfiles

Length of output: 191


🏁 Script executed:

cat -n home-manager/services/tmux-session-logger/default.nix

Repository: shunkakinoki/dotfiles

Length of output: 1809


🏁 Script executed:

cat -n home-manager/programs/tmux/default.nix

Repository: shunkakinoki/dotfiles

Length of output: 632


🏁 Script executed:

cat -n home-manager/services/default.nix

Repository: shunkakinoki/dotfiles

Length of output: 1149


Script path will fail at runtime; use the installed home directory location instead.

The relative path ../../programs/tmux/session-logger.sh resolves to a source tree location during Nix evaluation. At runtime on a deployed system, this source path will not exist, causing the service to fail.

The script is already installed to ~/.config/tmux/session-logger.sh by the programs.tmux module. Reference that location instead:

🔧 Proposed fix
-  sessionLoggerScript = ../../programs/tmux/session-logger.sh;
+  sessionLoggerScript = "$HOME/.config/tmux/session-logger.sh";

Or use the XDG base directory variable:

-  sessionLoggerScript = ../../programs/tmux/session-logger.sh;
+  sessionLoggerScript = "\${XDG_CONFIG_HOME:-$HOME/.config}/tmux/session-logger.sh";
📝 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
sessionLoggerScript = ../../programs/tmux/session-logger.sh;
sessionLoggerScript = "${XDG_CONFIG_HOME:-$HOME/.config}/tmux/session-logger.sh";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@home-manager/services/tmux-session-logger/default.nix` at line 4, The
sessionLoggerScript currently points to a source-tree path that won't exist at
runtime; update the sessionLoggerScript assignment to reference the installed
user location instead (e.g. ~/.config/tmux/session-logger.sh or
$XDG_CONFIG_HOME/tmux/session-logger.sh) so the tmux-session-logger service uses
the file installed by programs.tmux rather than
../../programs/tmux/session-logger.sh.

servicePath = lib.makeBinPath [
pkgs.bash
pkgs.coreutils
pkgs.tmux
];
in
{
launchd.agents.tmux-session-logger = lib.mkIf pkgs.stdenv.isDarwin {
enable = true;
config = {
ProgramArguments = [
"${pkgs.bash}/bin/bash"
"${sessionLoggerScript}"
];
Environment = {

@cubic-dev-ai cubic-dev-ai Bot Mar 19, 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.

P1: Use EnvironmentVariables instead of Environment in the launchd job config; otherwise PATH is not applied to the agent process.

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

<comment>Use `EnvironmentVariables` instead of `Environment` in the launchd job config; otherwise PATH is not applied to the agent process.</comment>

<file context>
@@ -0,0 +1,54 @@
+        "${pkgs.bash}/bin/bash"
+        "${sessionLoggerScript}"
+      ];
+      Environment = {
+        PATH = "${servicePath}:/usr/bin:/bin:/usr/sbin:/sbin";
+      };
</file context>
Fix with Cubic

PATH = "${servicePath}:/usr/bin:/bin:/usr/sbin:/sbin";
};
RunAtLoad = true;
StartInterval = 30;
StandardOutPath = "/tmp/tmux-session-logger.log";
StandardErrorPath = "/tmp/tmux-session-logger.error.log";
};
};

systemd.user.services.tmux-session-logger = lib.mkIf pkgs.stdenv.isLinux {
Unit = {
Description = "Persist tmux pane history snapshots";
};
Service = {
Type = "oneshot";
Environment = "PATH=${servicePath}";
ExecStart = "${pkgs.bash}/bin/bash ${sessionLoggerScript}";
};
};

systemd.user.timers.tmux-session-logger = lib.mkIf pkgs.stdenv.isLinux {
Unit = {
Description = "Timer for tmux session history logging";
};
Timer = {
OnBootSec = "1s";
OnUnitActiveSec = "30s";
AccuracySec = "1s";
Unit = "tmux-session-logger.service";
};
Install = {
WantedBy = [ "timers.target" ];
};
};
}
Loading