Skip to content

fix(tui): bind side-copy cleanup to process lifetime - #3783

Open
me2seeks wants to merge 2 commits into
apache:mainfrom
me2seeks:fix/session-copy-owner-incarnation
Open

fix(tui): bind side-copy cleanup to process lifetime#3783
me2seeks wants to merge 2 commits into
apache:mainfrom
me2seeks:fix/session-copy-owner-incarnation

Conversation

@me2seeks

@me2seeks me2seeks commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #3782

Bind temporary Session-copy cleanup ownership to an OS-held process-lifetime file lock instead of relying on PID liveness alone.

The TUI now persists an opaque lifetime reference alongside the legacy PID, groups leases belonging to one process incarnation, and holds one recovery claim throughout Host-side removal. Lock inspection failures fail closed, and records written by older clients continue to use the existing PID fallback.

Verification

  • npm --workspace @maka/storage run build
  • npm --workspace @maka/storage run typecheck
  • Affected storage tests: 21 passed
  • npm --workspace maka-agent run build
  • CLI tests: 459 passed
  • npm run lint
  • npm run format:check
  • npm run check:asf-headers
  • git diff --check

Review focus

The Runtime Host remains the authority that removes Sessions, SQLite remains the durable cleanup-intent store, and the native lock answers only whether a particular TUI process incarnation is still alive. No heartbeat, timer, new database table, or platform-specific process command is introduced.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Codex designed, implemented, tested, and reviewed the process-lifetime cleanup ownership change.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@me2seeks
me2seeks force-pushed the fix/session-copy-owner-incarnation branch from 3cb1c74 to ca943ea Compare August 25, 2026 16:06
@me2seeks
me2seeks marked this pull request as ready for review August 25, 2026 16:21

@jackwener jackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

APPROVED at ca943ea5574b5d4ed1dd164ed954c1476a12fff1: no P0-P2 findings; one non-blocking P3 follow-up.

Finding

[P3][reasonable crash/recovery path] Retire unreferenced owner lease files left by an unclean TUI exit.

acquireProcessLifetimeOwner() eagerly creates and locks a UUID .lease file before any Session-copy record necessarily references it. If the process is killed before it writes a copy lease—or after its last copy record is removed—the OS releases the lock but the file remains. Recovery discovers owner references only through workflow_quote_companion_cleanup; it never enumerates the owners directory, so a reference with zero database rows is never claimed or retired. A real-process probe killed such an owner, started and gracefully closed a successor, and the dead owner's file still remained as the sole entry. This leaks one inode per qualifying crash indefinitely. Either acquire lazily or, during recovery, enumerate only strictly valid versioned lease filenames and claim/retire those that have no database references; an active owner remains protected because its native lock cannot be claimed. Add a zero-record crash regression. This is storage litter rather than a Session-safety failure, so it is non-blocking.

Evidence and gates

  • Reviewed head/base: ca943ea5574b5d4ed1dd164ed954c1476a12fff1 / e28ce6f515471d59b9063dd8a951db82e58f8fea.
  • New records are classified by ownerLifetimeRef before the legacy PID branch, so PID reuse cannot affect their liveness decision. Older PID-only records retain the existing fallback.
  • Real-process tests prove a live owner cannot be claimed, the lock becomes claimable after SIGKILL, and only one concurrent recovery claimant wins. A separate same-process dual-FD probe against fs-native-extensions 1.5.0 returned true / false / false (first lock / second lock / retry after closing the second FD), confirming that closing an unrelated FD does not release the held lease. The dependency uses flock on macOS, F_OFD_SETLK on Linux, and LockFileEx on Windows.
  • Reference parsing reconstructs paths only from a strict UUID capture; O_NOFOLLOW plus regular-file and dev/inode checks rejects symlink/path replacement, and lock-inspection failures retain the Session copy.
  • Exact-head hosted test, audit, owner (macos-latest), and owner (windows-latest) checks all completed successfully.
  • Current main was 19b47632be61b9116ae25d67e286f6a7878d6645; the synthetic merge was clean (tree 016a0d014332a60b1fde19db5a0e8f69f65b2137), completed the full build:test, and passed 90/90 focused owner/cleanup/Host/conversation-copy tests. This includes the new main conversation-copy changes from #3806.

This approval does not merge the PR.

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the production change. No P0 or P1 — the approval stands and this is ready to merge as far as I am concerned. Three comments inline: one P2 and two P3, none blocking.

What holds up well:

  • openStableNativeLockFile pairs O_NOFOLLOW with a two-way dev/ino comparison between the handle and the path, so the lease cannot be swapped underneath the lock.
  • The lock-v1: prefix plus the regex in ownerPath means a reference read back out of SQLite cannot escape owners/.
  • The unlink-before-release ordering in close() is correct, and the comment explaining it is accurate. I worked the reverse order through: releasing first lets a second claimant lock the still-named inode, and after the unlink a third can create and lock a fresh one, giving two live claimants for one reference. Unlinking first closes that.
  • Extracting native-file-lock.ts and rewriting process-lifetime-file-update-lock.ts onto it is the right seam — one lock primitive rather than a second copy.
  • apps/desktop/src/main/runtime-host-boot.ts:791 builds a SessionCopyCleanupAuthority without a processLifetimeOwner, which I checked and is fine: its workspaceRoot is the Host workspace, not <clientDataRoot>/tui-session-copies/<rootId>, so the two authorities never read each other's records.

One observation, not a graded finding: acquireProcessLifetimeOwner is now awaited unconditionally in createRuntimeHostTuiContext, so a failure to create or lock the lease aborts TUI startup for users who never open a side conversation. fs-native-extensions was already a hard dependency of the CLI startup path via runtime-host-service-manager.ts, so this is not a new dependency risk, but it is a newly unconditional one against clientDataRoot. Failing closed is a defensible choice and I could not point to a reachable filesystem where it breaks, so I am recording it rather than asking for a change.

AI use: drafted with Claude Code (Opus 5); I verified every claim in it against the source at ca943ea55 before posting.

Comment thread packages/storage/src/session-copy-cleanup.ts Outdated
Comment thread packages/storage/src/process-lifetime-owner.ts
Comment thread packages/storage/src/session-copy-cleanup.ts
Use an OS-held native file lock as the process-incarnation identity for temporary Session-copy cleanup. Preserve PID-only recovery for leases written by older clients and hold one recovery claim across Host-side removal.

Generated-by: Codex
@me2seeks
me2seeks force-pushed the fix/session-copy-owner-incarnation branch from ca943ea to ffa5690 Compare August 26, 2026 04:27
@me2seeks

Copy link
Copy Markdown
Contributor Author

Addressed the approved review's orphan-lease follow-up and all three inline findings in ffa56901c. Detailed responses are inline.

Validation: root build; affected owner/cleanup tests 21/21; full storage suite 969 passed and 16 skipped; CLI tests 459/459; lint; format; ASF headers; git diff --check.

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I reviewed this head and found no blocking issues.

Owner lock correctly uses incarnation-aware UUID + lifetime reference grouping under single claim; cleanup/cancel states not mis-reclaimed; PID fallback retained for legacy leases. Hosted test/audit/owner SUCCESS.

No P0-P3.

简体中文该头无阻断。

Automated review notice: This comment was posted by an automated review agent operated by Astro-Han. It is not an independent human review and does not replace one.

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.

TUI side-copy cleanup can preserve stale leases after PID reuse

3 participants