Skip to content

[bug] CLAUDE.md regeneration silently clobbers PR-driven rule edits #240

@Jason-Vaughan

Description

@Jason-Vaughan

Summary

TC's per-project CLAUDE.md is regenerated from the DB-stored global rules on every session launch. PR-driven raw-file edits to CLAUDE.md are silently discarded the next time TC restarts or syncs a project, with no warning to the operator. The rule additions live on in git log but are functionally absent from the live ruleset.

This trap bit the 2026-05-23 session on TC-v3 itself: PRs #234 (versioning bump-level table) and #236 (stale-plan archive rule) merged cleanly, their CLAUDE.md deltas landed in main, and both were stripped on the next TC restart. The "structural lessons codified in CLAUDE.md" claim we celebrated in those PR descriptions was technically true in git history and functionally false in the running ruleset.

Root cause

lib/engines.js#_generateClaudeMd (~line 341) composes CLAUDE.md from:

  1. data/default-global-rules.md (factory default, 11 lines — only the seed for fresh installs)
  2. The user's customized global rules stored at _globalRulesPath() (loaded via store.globalRules.load())
  3. Per-project + methodology data

The regeneration runs on:

  • Every session launch (lib/sessions.js#launchSession)
  • Every project create/update via API (lib/projects.js#updateProject when engine/methodology changes)
  • The startup sync at lib/projects.js#syncAllProjects()

There is no merge between the on-disk CLAUDE.md and the regenerated content. The file is overwritten in place.

How to detect if you're affected

Run this in your TC-v3 clone:

# All commits that touched CLAUDE.md
git log main --oneline -- CLAUDE.md

# For each commit that added a `**Rule:` or `##` block, check whether
# the same text appears in the live ruleset. If not, your PR was clobbered.
curl -sk https://localhost:3102/api/rules/global \
  | python3 -c "import json, sys; print(json.load(sys.stdin)['content'])" \
  > /tmp/tc-db-rules.md

# Spot-check known PR-added rules:
grep "Archive plans whose chunk has shipped" /tmp/tc-db-rules.md  # #236 — should match after recovery
grep "TC's .version-bump. wrap step picks the bump level" /tmp/tc-db-rules.md  # #234 — should match after recovery

If any rule from git log is missing from /tmp/tc-db-rules.md, it was silently clobbered.

How to recover (if you're affected)

The rules from #234 and #236 have been re-merged into the DB content via store.globalRules.save() on the TC-v3 dev clone — but other clones still need to recover on their own. Two paths:

Option 1 — recover by re-applying via the API (one-shot per clone):

# Pull the rule blocks from the PR diffs:
git show 463e57a -- CLAUDE.md  # #234 bump-level rule + table
git show 8830e5c -- CLAUDE.md  # #236 archive-plans rule + issue-state check

# Splice them into your current rules and PUT back:
curl -sk https://localhost:3102/api/rules/global  # read current content
# … edit locally, then …
# (Note: PUT /api/rules/global has a 10KB body limit per #235-era constants;
#  if your merged content exceeds it, write via a small node script that
#  calls store.globalRules.save(content) directly. See the recovery PR for
#  the exact pattern.)

Option 2 — recover via the landing-page editor: paste the rule blocks into the global-rules editor (gear icon → Global Rules) and save.

Either path puts the content into the DB, where it survives regeneration.

Other engine configs affected (same bug, less commonly hit)

_generate* functions in lib/engines.js cover Claude (CLAUDE.md), Codex (.codex.yaml), Aider (.aider.conf.yml), Gemini (GEMINI.md). All four are regenerated. PR-driven edits to any of them have the same fate as CLAUDE.md. In practice CLAUDE.md is the most hand-edited because TC-v3 itself uses Claude Code.

Proposed structural fix (separate work)

Multiple design options, none decided yet:

  • (A) Tracked-file canonical source. Make data/default-global-rules.md (or a renamed equivalent) the canonical source. TC's startup sync detects when the file is newer than the DB-stored content and offers to merge or auto-applies non-conflicting additions. PR-driven rule changes edit the tracked file; existing user customizations stored in the DB are preserved via a merge strategy.
  • (B) Append-only marker pattern. TC's regeneration preserves content below a <!-- @user-additions --> marker in CLAUDE.md. PR-driven edits go below the marker. Loses the architectural pretense that the file is "generated" but works for incremental additions.
  • (C) Pre-commit / pre-merge guard. A git hook in TC's own repo (or a CI check) blocks commits that touch CLAUDE.md and tells the contributor to use the API or the landing-page editor. Prevents the trap but doesn't fix the architecture.
  • (D) Detect-and-warn on regeneration. On overwrite, diff the existing CLAUDE.md against the regenerated content. If they differ in non-trivial ways (operator-added rules vs whitespace), log a loud warning AND surface in a banner. Operator decides how to reconcile.

Recommendation pending. I'll open this as a discussion thread; the design call belongs to whoever picks up the structural work.

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions