<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Logging on Code is cheap, let&#39;s talk</title>
    <link>https://blog.ferstar.org/en/tags/logging/</link>
    <description>Code is cheap, let&#39;s talk</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <copyright>Copyright 2026 ferstar</copyright>
    <lastBuildDate>Sat, 20 Jun 2026 16:55:00 +0800</lastBuildDate>
    <ttl>60</ttl><atom:link href="https://blog.ferstar.org/en/tags/logging/index.xml" rel="self" type="application/rss+xml" /><image>
      <url>https://blog.ferstar.org/site-logo.png</url>
      <title>Code is cheap, let&#39;s talk</title>
      <link>https://blog.ferstar.org/</link>
    </image>
    
    <item>
      <title>Stopping Codex SQLite Log Growth with a Trigger</title>
      <link>https://blog.ferstar.org/en/posts/codex-sqlite-log-trigger/</link>
      <pubDate>Sat, 20 Jun 2026 16:55:00 +0800</pubDate>
      
      <guid isPermaLink="true">https://blog.ferstar.org/en/posts/codex-sqlite-log-trigger/</guid>
      <description>Codex&#39;s local SQLite log database can grow too fast; a small BEFORE INSERT trigger blocks new log rows, keeps a restore path, and quickly reduces disk IO and WAL growth.</description><content:encoded><![CDATA[<blockquote><p>I am not a native English speaker; this article was translated by AI.</p>
</blockquote><p>Codex recently started storing local logs in <code>~/.codex/logs_2.sqlite</code>. On my machine that database had already grown past 1GB. The real problem was not the WAL file itself, but the log table: <code>TRACE</code>, <code>DEBUG</code>, and <code>INFO</code> rows kept going into SQLite, creating unnecessary disk usage and IO.</p>
<p>The public configuration surface is limited here. <code>RUST_LOG</code> can reduce verbosity, <code>log_dir</code> only controls the plaintext TUI log, and <code>history.max_bytes</code> only applies to <code>history.jsonl</code>. I could not find a public retention, max-size, or journal-mode option for <code>logs_2.sqlite</code>.</p>
<p>So I used SQLite itself as the stopgap.</p>

<h3 class="relative group">Block new log rows with one trigger
    <div id="block-new-log-rows-with-one-trigger" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#block-new-log-rows-with-one-trigger" aria-label="Anchor">#</a>
    </span>
    
</h3>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sqlite3 ~/.codex/logs_2.sqlite <span class="s2">"CREATE TRIGGER IF NOT EXISTS block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;"</span></span></span></code></pre></div></div>
<p>The trigger is intentionally blunt: whenever something tries to insert into the <code>logs</code> table, SQLite ignores that insert.</p>
<p>Verification is also simple:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sqlite3 ~/.codex/logs_2.sqlite <span class="s2">"
</span></span></span><span class="line"><span class="cl"><span class="s2">SELECT count(*) FROM logs;
</span></span></span><span class="line"><span class="cl"><span class="s2">INSERT INTO logs(ts, ts_nanos, level, target, feedback_log_body, estimated_bytes)
</span></span></span><span class="line"><span class="cl"><span class="s2">VALUES(strftime('%s','now'), 0, 'INFO', 'trigger_test', 'should_not_exist', 1);
</span></span></span><span class="line"><span class="cl"><span class="s2">SELECT count(*) FROM logs;
</span></span></span><span class="line"><span class="cl"><span class="s2">SELECT count(*) FROM logs WHERE target='trigger_test';
</span></span></span><span class="line"><span class="cl"><span class="s2">"</span></span></span></code></pre></div></div>
<p>If the row count stays the same and <code>trigger_test</code> is <code>0</code>, the trigger is working.</p>

<h3 class="relative group">Windows PowerShell version
    <div id="windows-powershell-version" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#windows-powershell-version" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>On Windows, the path is usually:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nv">$db</span> <span class="p">=</span> <span class="nb">Join-Path</span> <span class="nv">$env:USERPROFILE</span> <span class="s2">".codex\logs_2.sqlite"</span>
</span></span><span class="line"><span class="cl"><span class="n">sqlite3</span> <span class="nv">$db</span> <span class="s2">"CREATE TRIGGER IF NOT EXISTS block_log_inserts BEFORE INSERT ON logs BEGIN SELECT RAISE(IGNORE); END;"</span></span></span></code></pre></div></div>
<p>I verified this on a remote Windows machine. The test insert did not change the row count:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">trigger: block_log_inserts
</span></span><span class="line"><span class="cl">before: 76387
</span></span><span class="line"><span class="cl">after: 76387
</span></span><span class="line"><span class="cl">trigger_test_rows: 0</span></span></code></pre></div></div>

<h3 class="relative group">Restore log writes
    <div id="restore-log-writes" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#restore-log-writes" aria-label="Anchor">#</a>
    </span>
    
</h3>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sqlite3 ~/.codex/logs_2.sqlite <span class="s2">"DROP TRIGGER IF EXISTS block_log_inserts;"</span></span></span></code></pre></div></div>
<p>PowerShell:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nv">$db</span> <span class="p">=</span> <span class="nb">Join-Path</span> <span class="nv">$env:USERPROFILE</span> <span class="s2">".codex\logs_2.sqlite"</span>
</span></span><span class="line"><span class="cl"><span class="n">sqlite3</span> <span class="nv">$db</span> <span class="s2">"DROP TRIGGER IF EXISTS block_log_inserts;"</span></span></span></code></pre></div></div>

<h3 class="relative group">Compact the old logs too
    <div id="compact-the-old-logs-too" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#compact-the-old-logs-too" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>The trigger only blocks new rows. It does not shrink a database that has already grown. After quitting Codex, run a checkpoint and <code>VACUUM</code>:</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sqlite3 ~/.codex/logs_2.sqlite <span class="s2">"
</span></span></span><span class="line"><span class="cl"><span class="s2">PRAGMA wal_checkpoint(TRUNCATE);
</span></span></span><span class="line"><span class="cl"><span class="s2">DELETE FROM logs WHERE level IN ('TRACE','DEBUG');
</span></span></span><span class="line"><span class="cl"><span class="s2">DELETE FROM logs WHERE level = 'INFO' AND ts < strftime('%s','now','-3 days');
</span></span></span><span class="line"><span class="cl"><span class="s2">VACUUM;
</span></span></span><span class="line"><span class="cl"><span class="s2">"</span></span></span></code></pre></div></div>
<p>If Codex is still running, SQLite may report <code>database is locked</code>. That is expected. Quit Codex and run it again.</p>

<h3 class="relative group">The limit of this trick
    <div id="the-limit-of-this-trick" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-limit-of-this-trick" aria-label="Anchor">#</a>
    </span>
    
</h3>
<p>This does not fix Codex’s logging system. It is just local damage control.</p>
<p>The upside is that it needs no Codex patch, no release wait, and no background cleanup daemon. The downside is that <code>logs_2.sqlite</code> will no longer contain new local logs, which makes local debugging weaker. When you need logs, drop the trigger, reproduce the issue, then add the trigger again.</p>
<p>Long term, Codex should expose log database retention or max-size settings. Until then, one SQLite trigger is enough.</p>
]]></content:encoded>
      
    </item>
    
  </channel>
</rss>
