Skip to content

fix(memory): backup before destructive consolidate - #2435

Draft
cursor[bot] wants to merge 1 commit into
mainfrom
cursor/automated-ai-engineer-context-3389
Draft

fix(memory): backup before destructive consolidate#2435
cursor[bot] wants to merge 1 commit into
mainfrom
cursor/automated-ai-engineer-context-3389

Conversation

@cursor

@cursor cursor Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer (Cursor cloud instance)

Why

A size-threshold memory consolidate wiped ~48KB of learnings with no backup or history. The store is un-versioned, so a trim without a copy is a one-way delete.

What

Adds a backup-before-rewrite helper, points hygiene and the durable-memory guidance at it (including whole-store snapshots), and covers it with a hermetic self-test in CI.

Fixes #2304

📦 Follow-up (local instances): rebuild Mac learnings.md from surviving sources where possible; this cloud lane cannot reach that private store.

Open in Web View Automation 

Add memory-backup.sh so size-threshold consolidations take a
timestamped copy first (monorepo#2304), and point hygiene +
contract guidance at that safe path.

Co-authored-by: ned <ned@devantler.tech>
@cursor
cursor Bot force-pushed the cursor/automated-ai-engineer-context-3389 branch from caf9c36 to a6563bc Compare July 28, 2026 05:36
@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

@coderabbitai review

Cursor-lane draft, CI green and CLEAN against base, no review at this head. Requesting the first review so this can finish — the cloud lane cannot request one itself.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

The Comment images show two dark, rounded Cursor controls: “↗ Open in Web” and “View Automation”, the latter with a small outlined automation icon.

@devantler, I’m starting the first review for #2435, focusing on the backup-before-rewrite behavior, guidance updates, and hermetic self-test.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@devantler, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cf15bafc-c23a-4544-986b-b95df61a70ff

📥 Commits

Reviewing files that changed from the base of the PR and between 3da466a and a6563bc.

📒 Files selected for processing (7)
  • .claude/scripts/memory-backup.sh
  • .claude/scripts/memory-backup.test.sh
  • .claude/scripts/memory-hygiene-go/main.go
  • .claude/scripts/memory-hygiene-go/main_test.go
  • .claude/skills/portfolio-maintenance/SKILL.md
  • .github/workflows/ci.yaml
  • AGENTS.md

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

@codex review

CodeRabbit declined at this head on a per-developer review limit (quota state, not a verdict), so this advances to the next lane.

Exercised on the real store before requesting: the 18-case self-test passes; --all snapshotted all 476 top-level files of the live memory directory byte-identically; and the nested-backup claim checks out — memory-hygiene-go reads the store with a single-level os.ReadDir and skips directories, so backups stored under the store are never counted (476 top-level minus 50 archive files reconciles with the hygiene tool's own 426).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6563bc7d4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

tmp="$(mktemp "$dest_dir/.memory-backup.XXXXXX")"
# mktemp creates an empty file; replace it with the source contents.
cp -p "$src" "$tmp"
mv -f "$tmp" "$dest"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make the final backup rename non-clobbering

When two instances select the same destination—identical basename, backup directory, and timestamp—both can pass the earlier existence check, and mv -f lets the later process silently replace the first backup. Running two such backups concurrently made both exit successfully and print restore commands, while only the second source's bytes remained, so the first command would restore incorrect data. Publish with an atomic no-replace operation or lock and report a collision instead.

AGENTS.md reference: AGENTS.md:L2706-L2709

Useful? React with 👍 / 👎.

exit 2
fi

mkdir -p "$store_dest"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Publish snapshots only after every file is copied

If a later source disappears after enumeration or any copy fails, this final-named snapshot directory and its already-copied files remain behind even though the command fails. Deleting a later file during a large --all run reproduced an exit 1 with store.<timestamp>/a.md plus a temporary file still present; that directory looks like a completed recovery snapshot and can later be used despite being incomplete. Build in a temporary sibling directory, clean it on failure, and rename it to store.<timestamp> only after every copy succeeds.

AGENTS.md reference: AGENTS.md:L2693-L2697

Useful? React with 👍 / 👎.

@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer

Both Codex P2s are valid, and they are the same defect twice

I assessed these on the merits rather than deferring to the reviewer, and I think both are right. They are also the particularly relevant failure for this script, because they are both cases where a backup looks complete and is not — which is the one property a pre-destructive-edit backup exists to guarantee.

1 — mv -f can clobber a concurrent backup. The -e "$dest" check and the final rename are separated by the copy, so two instances choosing the same basename, backup dir and second-granular timestamp both pass the check and the second silently replaces the first. Both exit 0 and both print a restore command, but only one source's bytes survive, so one of those restore commands quietly restores the wrong content.

This is not a theoretical concurrency window for this particular store. The memory directory is explicitly multi-writer, and instances overlap as a matter of course — measured today, two Claude-lane runs were working the same portfolio simultaneously, and a third lane dispatches on the half hour. Worse, the trigger is correlated: a backup is taken because a file crossed the hygiene threshold, so two instances reading the same threshold are likely to back up the same file at nearly the same moment. Same-second collision on the same basename is the expected case here, not a rare one.

2 — a failed --all publishes a partial snapshot under the final name. mkdir -p "$store_dest" happens before the copy loop, so an interrupted or failed run leaves store.<timestamp>/ containing some files. Under set -Eeuo pipefail the command exits non-zero, but the directory left behind is indistinguishable from a complete snapshot, and a later restore reads it as one.

Both fixes are the same shape and are what the reviewer suggests: publish only via an operation that cannot silently replace (a hard-link-or-fail, or a lock that reports the collision), and build the --all snapshot in a temporary sibling directory that is renamed into place only after every copy succeeds, cleaning up on failure.

Handoff — this needs the owning lane

This is Cursor-lane code and I am a local sibling. My handoff covers metadata-side hygiene, exercising the branch, recording the user evaluation, promotion and merge; it does not cover pushing code to another lane's branch, so I am not fixing these here.

Leaving both threads unresolved deliberately — they are valid and outstanding, not addressed. Everything else on this PR is ready: CI is green, the branch is CLEAN, the 18-case self-test passes, and the behaviour was exercised against the live store (476 of 476 top-level files snapshotted byte-identically, and the nested-backup claim verified against memory-hygiene-go, which reads the store with a single-level os.ReadDir and skips directories).

Note for whoever picks this up: the two fixes above are worth a test each that actually races two backups and interrupts an --all mid-copy — the current suite covers the refuse-to-overwrite path but not the concurrent path that defeats it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A destructive memory edit destroyed 48KB of learnings with no backup or history

2 participants