majiayu000/codex-log-guard
Diagnose excessive Codex local SQLite diagnostic log writes with read-only evidence by default. Use when a user mentions logs_2.sqlite, logs_2.sqlite-wal, block_log_inserts, SSD/TBW wear, or explicitly asks to protect, clean up, verify, or restore Codex diagnostic logging.
npx skills add https://github.com/majiayu000/spellbook --skill codex-log-guard
Diagnose Codex persistent diagnostic logging from local evidence, then give a concise conclusion and the safest next action. Do not make the user choose from a command menu.
Select one mode from the current user request:
diagnose_only is the default for check, inspect, explain, or verify requests. It is read-only.protect requires an explicit request to stop or mitigate log writes. It may install and verify block_log_inserts, but it does not delete rows or vacuum files.cleanup requires an explicit current request to reclaim disk space or clean up logs. It first installs protection when needed, creates and verifies a timestamped backup, and only then deletes log rows and vacuums.restore requires an explicit request to resume diagnostic logging. It may drop only the known block_log_inserts trigger.Generic wording such as "处理", "修一下", or "止血" selects protect, not cleanup. Prior approval does not carry into a later run. If the requested write mode is ambiguous, return the diagnose_only report and the exact proposed mutation without applying it.
Direct actions:
Escalate before:
Evidence-backed pushback:
Feedback loop:
agents/openai.yaml contains discovery UI metadata only; it is not an operational instruction source.
When the user asks to "check", "看看", "诊断", or asks whether the local machine is affected:
~/.codex/logs_2.sqlite~/.codex/sqlite/logs_2.sqliteblock_log_inserts already exists on each candidate with a logs table.logs is still being written using COUNT(*), MIN(id), MAX(id) samples.Treat MAX(id) or MIN(id) movement with stable COUNT(*) as active churn, not necessarily disk growth.
TRACE/DEBUG counts and top noisy targets.In protect mode:
block_log_inserts first.COUNT(*), MAX(id) stops growing.In cleanup mode:
.backup and require a non-empty file plus a successful PRAGMA quick_check result.In restore mode:
block_log_inserts.COUNT(*), MAX(id) to confirm logging resumes or stays quiet.Run direct shell/SQLite commands. Use only the needed subset for the user's request; do not paste a menu back to the user.
Inspect files:
for db in ~/.codex/logs_2.sqlite ~/.codex/sqlite/logs_2.sqlite; do
ls -lh "$db"* 2>/dev/null
du -h "$db"* 2>/dev/null
done
After lsof identifies the active candidate, validate the selected path in the
same shell command before running any later SQLite snippet:
: "${CODEX_LOG_DB:?set CODEX_LOG_DB to the verified active candidate}"
case "$CODEX_LOG_DB" in
"$HOME/.codex/logs_2.sqlite"|"$HOME/.codex/sqlite/logs_2.sqlite") ;;
*) echo "refusing unexpected Codex log database path" >&2; exit 2 ;;
esac
readonly db="$CODEX_LOG_DB"
Do not supply a default. If no active path can be proven, stay in
diagnose_only and report the ambiguity.
Check schema and trigger:
sqlite3 "$db" ".tables"
sqlite3 "$db" "PRAGMA table_info(logs);"
sqlite3 "$db" "SELECT name, tbl_name, sql FROM sqlite_master WHERE type='trigger' AND name='block_log_inserts';"
Sample writes and growth:
for i in 1 2 3; do
date '+%F %T'
sqlite3 "$db" "SELECT COUNT(*) AS rows, MIN(id) AS min_id, MAX(id) AS max_id FROM logs;"
stat -f '%N %z bytes mtime=%Sm' "$db" "$db-wal" "$db-shm" 2>/dev/null
sleep 10
done
Inspect levels and noisy targets:
sqlite3 "$db" "SELECT level, COUNT(*) AS n, ROUND(SUM(estimated_bytes)/1024.0/1024.0, 1) AS estimated_mib FROM logs GROUP BY level ORDER BY n DESC;"
sqlite3 "$db" "SELECT target, level, COUNT(*) AS n, ROUND(SUM(estimated_bytes)/1024.0/1024.0, 1) AS estimated_mib FROM logs GROUP BY target, level ORDER BY n DESC LIMIT 15;"
Check open processes:
lsof ~/.codex/logs_2.sqlite ~/.codex/logs_2.sqlite-wal ~/.codex/logs_2.sqlite-shm \
~/.codex/sqlite/logs_2.sqlite ~/.codex/sqlite/logs_2.sqlite-wal ~/.codex/sqlite/logs_2.sqlite-shm 2>/dev/null
Install protection:
sqlite3 "$db" "PRAGMA busy_timeout=10000; CREATE TRIGGER IF NOT EXISTS block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;"
Clean up after protection:
backup="$db.bak.$(date +%Y%m%d-%H%M%S)"
sqlite3 "$db" ".backup '$backup'"
test -s "$backup"
test "$(sqlite3 "$backup" 'PRAGMA quick_check;')" = "ok"
sqlite3 "$db" "PRAGMA busy_timeout=10000; PRAGMA wal_checkpoint(TRUNCATE); DELETE FROM logs; VACUUM; PRAGMA wal_checkpoint(TRUNCATE);"
echo "$backup"
Restore persistent logging:
sqlite3 "$db" "DROP TRIGGER IF EXISTS block_log_inserts;"
logs_2.sqlite files: healthy/not applicable unless the user expects Codex to have run.lsof; do not assume the top-level path is the only live database.COUNT/MIN(id)/MAX(id) stable: protected.MIN(id) or MAX(id) moves: affected and actively writing.TRACE/DEBUG, but no sample movement: affected historically; recommend protection, cleanup optional.logs is absent or schema differs, stop and report that the known workaround is not safely applicable.COUNT(*), MAX(id) sampling.cleanup as reversible only through its timestamped backup. Mention the backup path in the final answer.~/.codex/logs_2.sqlite* and ~/.codex/sqlite/logs_2.sqlite*); it does not manage conversation archives, repo files, credentials, or remote telemetry.COUNT(*) can stay constant while MIN(id) and MAX(id) move; classify this as churn, not a quiet database.Keep the user-facing answer short:
Take majiayu000/codex-log-guard from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.