From 478be56679cb49d91bedf9c4316e2b25f1c22ce9 Mon Sep 17 00:00:00 2001 From: Godopu Date: Sun, 21 Jun 2026 08:43:06 +0000 Subject: [PATCH] fix(lib): hardening and edge-case bugfixes (FW-12, FW-16 round) - Restored .bak generation to maintain P0-B backup invariants - Fixed stale NFS warning message to reflect SQLite DELETE fallback - Replaced vulnerable yaml.replace with os.path.splitext globally - Ensured YAML dump occurs after conn.commit() to prevent partial syncs - Re-applied chmod 0600 on SQLite -wal and -shm files --- skills/lib.sh | 42 ++++++++++++------- .../scripts/reconcile.sh | 2 +- .../scripts/status.sh | 2 +- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/skills/lib.sh b/skills/lib.sh index 20efa4a..c5df149 100644 --- a/skills/lib.sh +++ b/skills/lib.sh @@ -112,7 +112,7 @@ resolve_tmux_server() { import os, sys, sqlite3, json, yaml name = os.environ['SESSION_NAME'] yaml_path = os.environ['YAML_PATH'] -db_path = yaml_path.replace('.yaml', '.db') +db_path = os.path.splitext(yaml_path)[0] + '.db' d = {} try: if os.path.exists(db_path): @@ -124,13 +124,13 @@ try: with open(yaml_path) as f: d = yaml.safe_load(f) or {} for s in d.get('tmux_sessions', []): - if s.get('name') == name: - server = s.get('tmux_server') - if server: - print(server) - sys.exit(0) - except Exception: - pass + if s.get('name') == name: + server = s.get('tmux_server') + if server: + print(server) + sys.exit(0) +except Exception: + pass # Fallback print(os.environ.get('TMUX_SERVER_NAME', 'default')) PYEOF @@ -208,7 +208,7 @@ _atomic_dump_yaml_check_nfs() { if mount | grep -q "$mountpoint.*nfs\|$mountpoint.*cifs\|$mountpoint.*fuse.sshfs"; then echo "WARNING: $mountpoint appears to be a network filesystem (NFS/CIFS/SSHFS)." >&2 echo "WARNING: fcntl.flock-based atomic writes are unreliable on network filesystems." >&2 - echo "WARNING: Consider migrating to SQLite WAL for registry storage (see FUTURE_WORKS.md FW-02)." >&2 + echo "WARNING: SQLite journal_mode automatically falls back to DELETE." >&2 fi } @@ -228,7 +228,7 @@ from datetime import datetime, timezone import yaml yaml_path = os.environ['YAML_PATH'] -db_path = yaml_path.replace('.yaml', '.db') +db_path = os.path.splitext(yaml_path)[0] + '.db' def _validate(d): if not isinstance(d, dict): @@ -305,14 +305,16 @@ try: new_terminals = get_terminal_set(d) + conn.commit() + # Write to YAML ONLY when a session transitions to a finished state + # (Moved after conn.commit() per Claude's feedback) if new_terminals != old_terminals: if os.path.exists(yaml_path): try: shutil.copy2(yaml_path, yaml_path + '.bak') except Exception: pass - dir_ = os.path.dirname(yaml_path) or '.' fd, tmp = tempfile.mkstemp(dir=dir_, prefix='.agent-sessions.', suffix='.tmp') try: @@ -324,9 +326,7 @@ try: if os.path.exists(tmp): os.remove(tmp) raise - conn.commit() - - if new_terminals != old_terminals: + try: conn.execute('PRAGMA wal_checkpoint(TRUNCATE)') except Exception: @@ -336,6 +336,16 @@ except Exception: raise finally: conn.close() + + # H3: Re-apply chmod 0600 after close to cover newly created -wal / -shm files + try: + os.chmod(db_path, 0o600) + wal = db_path + '-wal' + if os.path.exists(wal): os.chmod(wal, 0o600) + shm = db_path + '-shm' + if os.path.exists(shm): os.chmod(shm, 0o600) + except Exception: + pass PYEOF } @@ -364,7 +374,7 @@ ws = os.environ['WS_ABS'] agent = os.environ['AGENT'] home = os.environ['HOME_DIR'] yaml_path = os.environ['YAML_PATH'] -db_path = yaml_path.replace('.yaml', '.db') +db_path = os.path.splitext(yaml_path)[0] + '.db' claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects") d = {} @@ -483,7 +493,7 @@ is_already_stopped() { import os, yaml, sqlite3, json name = os.environ['SESSION_NAME'] yaml_path = os.environ['YAML_PATH'] -db_path = yaml_path.replace('.yaml', '.db') +db_path = os.path.splitext(yaml_path)[0] + '.db' d = {} try: if os.path.exists(db_path): diff --git a/skills/tmux-agent-orchestrate-monitor/scripts/reconcile.sh b/skills/tmux-agent-orchestrate-monitor/scripts/reconcile.sh index 9aedcf5..f929611 100755 --- a/skills/tmux-agent-orchestrate-monitor/scripts/reconcile.sh +++ b/skills/tmux-agent-orchestrate-monitor/scripts/reconcile.sh @@ -238,7 +238,7 @@ try: d except NameError: import sqlite3 - db_path = yaml_path.replace('.yaml', '.db') + db_path = os.path.splitext(yaml_path)[0] + '.db' d = {} try: if os.path.exists(db_path): diff --git a/skills/tmux-agent-orchestrate-status/scripts/status.sh b/skills/tmux-agent-orchestrate-status/scripts/status.sh index 92b5ce7..e701731 100755 --- a/skills/tmux-agent-orchestrate-status/scripts/status.sh +++ b/skills/tmux-agent-orchestrate-status/scripts/status.sh @@ -37,7 +37,7 @@ home = os.environ['HOME_DIR'] claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects") drift = json.loads(os.environ['DRIFT_JSON']) -db_path = yaml_path.replace('.yaml', '.db') +db_path = os.path.splitext(yaml_path)[0] + '.db' d = {} import sqlite3 try: