cd /news/ai-agents/12-files-in-4-out-the-secret-scannin… · home topics ai-agents article
[ARTICLE · art-127338] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

12 Files In, 4 Out: The Secret-Scanning Gate Between Claude Code's Audit Memos and My Obsidian Wiki

A developer built wiki-sync-improvements.sh, a shell script that gates audit reports and improvement plans generated autonomously by Claude Code before they are synced into a personal Obsidian wiki. The script filters 12 Markdown files down to 4 eligible audit/plan/curation documents and aborts the entire sync if any candidate matches an API-key pattern such as sk-* or ghp_*, rather than dropping only the offending file. It defaults to a dry-run preview and writes conflicting files as timestamped copies instead of overwriting existing notes.

by read5 min views1 publishedSep 12, 2026

Last time I wrote about handing a monitoring script exactly one root command. This post is the same question ― what do you let unattended automation touch? ― aimed at a different target: the plumbing that carries audit reports and improvement plans written by Claude Code itself into my human-facing Obsidian wiki. The gate I ended up with takes 12 Markdown files as input, lets 4 through, and refuses to write anything at all if a single one of them contains something that looks like an API key.

~/.claude/improvements/ accumulates audit reports and improvement plans that Claude Code writes during autonomous operation. Look inside and you find entries like this (excerpt from audit-2026-05-29.md):

| project | 状態 | 推奨アクション |
|---|---|---|
| **seo-affiliate-site** 🔴 | 63 files 未コミット(Like / コメント / AdSense 等のマネタイズ実装が宙吊り) | 機能単位で分割コミット |
| closet-os 🟢 | clean / 直近活発 | `.env.production.local` 1.9KB は gitignore 済だがバックアップ運用注意 |

That's personal project names, implementation progress, and even the existence of .env files, all spelled out. Separate from the pipeline that turns conversation logs into long-term memory, there is a distinct risk here: documents the AI generates on its own can carry secrets and personal information. A fragment of an API key pasted during debugging ending up quoted in an audit memo is an entirely plausible accident.

I needed something mechanical that would stop these memos before they got poured into the wiki. That's wiki-sync-improvements.sh.

The comment block at the top of the script summarizes the whole design.

#
#

The reason log.md is excluded is visible in the numbers. On my machine it's 2,316 lines and roughly 228 KB ― a chronological log, not something that fits the wiki's one-note-per-file granularity.

Target filtering is a shell case pattern.

for f in "$SRC"/*.md; do
  base="$(basename "$f")"
  case "$base" in
    audit-*.md|*-plan-*.md|*-curation-*.md)
      candidates+=("$f")
      ;;
  esac
done

The secret scan looks at every candidate first, and only then decides whether to stop.

SECRET_RE='(sk-[A-Za-z0-9_-]{16,}|ghp_[A-Za-z0-9]{20,})'
secret_hits=()
for f in "${candidates[@]}"; do
  if grep -E -q "$SECRET_RE" "$f"; then
    secret_hits+=("$f")
  fi
done
if [[ ${#secret_hits[@]} -gt 0 ]]; then
  echo "[abort] secrets detected — sync stopped" >&2
  for h in "${secret_hits[@]}"; do echo "  - $h" >&2; done
  exit 2
fi

Note: This is deliberately not "drop only the files that contain a secret." A single hit halts every candidate. Allowing partial syncs would force the script to make the call "skip the one file with the secret, pass the rest through" ― and a mistake in that call is the scariest failure mode here. So the threshold has exactly one setting: stop everything.

Writing defaults to dry. MODE="${1:-dry}" means running with no argument writes nothing and only prints a preview. In apply mode, if a file with the same name already exists at the destination, the contents are compared (cmp -s, effectively a hash comparison); a match is skipped, and a difference is written out as a separate timestamped file ― coexistence, not overwrite.

if cmp -s "$tmp" "$out"; then
  rm -f "$tmp"
  skipped=$((skipped+1))
  continue
fi
stamped="$DST/${base%.md}.$(ts_suffix).md"
mv "$tmp" "$stamped"

Running dry locally produced this:

$ ~/.claude/scripts/wiki-sync-improvements.sh dry
[dry] would write timestamped: audit-2026-05-29.md -> meta/improvements/audit-2026-05-29.<ts>.md
[dry] would write timestamped: plugin-curation-2026-05-30.md -> meta/improvements/plugin-curation-2026-05-30.<ts>.md
[dry] would write timestamped: seo-affiliate-commit-plan-2026-05-30.md -> meta/improvements/seo-affiliate-commit-plan-2026-05-30.<ts>.md
[dry] would write timestamped: wiki-cleanup-plan-2026-05-29.md -> meta/improvements/wiki-cleanup-plan-2026-05-29.<ts>.md
---
[done] planned=4 total_candidates=4 (dry run; no writes)

~/.claude/improvements/ actually holds 12 .md files. Only these 4 made it through the filter; the other 8 (log.md / README.md / next-session-todo.md / commands-consolidation-plan.md and so on) are out of scope.

That's where I noticed how commands-consolidation-plan.md gets handled. The filename contains "plan," so you'd expect it to qualify, but it never shows up as a candidate. The reason is the pattern.

*-plan-*.md

This means "-plan- followed by at least one more character before .md," so xxx-plan.md (where a dot immediately follows "plan") does not match. A file like xxx-plan-2026-05-30.md with a date suffix gets picked up, but an improvement plan whose author forgot the suffix silently falls out of scope. No error, no warning.

This skill's frontmatter says status: stale.

status: stale

By the Curator's rule (demoted to stale after 30 days unused), that means it hasn't been running in real operation for a while. So I tried to look inside the destination directory to check how far apply had actually gotten ― and tripped right there.

$ ls ~/Documents/claude-obsidian/wiki/meta/improvements/
ls: .../meta/improvements/: Interrupted system call
total 0

ls, find, and the Glob tool all kept returning the same Interrupted system call (EINTR) and could not enumerate the contents. The parent meta/ directory showed the same symptom. Meanwhile, existence checks on individual files went through.

$ [ -e .../meta/improvements/audit-2026-05-29.md ] && echo exists
exists

But cat on that very same file hung with no response and timed out at 600 seconds. It behaves a lot like a directory under iCloud sync where only the file metadata exists and the actual content is stuck as an undownloaded placeholder. I can't say for certain, but the factual state is: "4 files are treated as existing, yet the path to read their contents doesn't work from this session."

In other words, the secret scan and the halt-everything logic look sound on reading ― but the path for verifying afterwards that things "actually landed correctly in the wiki" is thin, and when it breaks, it's hard to notice. That's the reality that only showed up when I ran it. Writing a safety gate and being able to continuously verify that gate's output turned out to be two separate problems.

log.md (2,316 lines / ~228 KB) as-is would wreck the wiki's per-note granularity*-plan-*.md pattern doesn't match xxx-plan.md (no suffix)[dry] would write timestamped means "same-named file present or not," not "there is a diff" Next time I plan to cover verifying destination arrival from outside the gate using a hash ledger.

If you're piping AI-generated notes into a knowledge base of your own, how do you confirm they actually landed ― or do you trust the exit code and move on?

*Written by Lily — I ship iOS apps and automate my content stack with Claude Code.

Follow along: Portfolio · X · GitHub*

── more in #ai-agents 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/12-files-in-4-out-th…] indexed:0 read:5min 2026-09-12 ·