[Bug]: Checkpoint restore succeeds but leaves later edits in skip-worktree files
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 72/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- git, node.js, typescript
Hướng nghiên cứu
Start with the reproduction script in the issue and the restore command in apps/server/src/vcs/GitVcsDriver.ts (lines 1396–1411); compare its behavior for a skip-worktree path with the ordinary-file control. Read the checkpoint success handling in apps/server/src/orchestration-v2/CheckpointService.ts (lines 526–550) and the cited Git restore documentation. Done means restore returns success and both files match the checkpoint without clearing the flag, while sparse-checkout exclusions retain their intended behavior.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Summary
Reverting workspace files can report success while a tracked file marked skip-worktree keeps its later edits. A normal tracked file beside it restores correctly, but the flagged file does not when its checkpoint content matches HEAD. This was reproduced through the production checkpoint driver in a disposable Git worktree.
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
- Create a Git repository with two committed text files and add a detached worktree.
- Mark one file skip-worktree in that worktree, then capture a checkpoint through the production Git driver.
- Change both files and restore the checkpoint. Check the returned success value and both file contents.
- Clear the skip-worktree flag and restore again to verify the workaround.
Save the script below as repro-skip.mjs and run node repro-skip.mjs /path/to/t3code from any directory. It requires a checkout of the stated commit with its normal dependencies installed, Node with TypeScript source support and Git. It creates all fixture files, Git metadata, HOME, TMPDIR, and T3 state under one temporary directory and removes it on exit; it leaves the checkout unchanged. Exit 1 means the defect reproduced, 0 means expected behavior, and 2 means a setup or control failed. The final script reproduced deterministically in one recorded run; earlier focused probes established the same behavior.
Reproduction script: repro-skip.mjs
import fs from 'node:fs';
import path from 'node:path';
import { createRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
import { execFileSync } from 'node:child_process';
const checkout = path.resolve(process.argv[2] ?? '.');
const scratch = fs.mkdtempSync('/tmp/t3-diff-repro-');
for (const name of ['home', 'tmp', 'state']) fs.mkdirSync(path.join(scratch, name));
process.env.HOME = path.join(scratch, 'home');
process.env.TMPDIR = path.join(scratch, 'tmp');
process.env.T3CODE_HOME = path.join(scratch, 'state');
process.env.GIT_CONFIG_NOSYSTEM = '1';
process.env.GIT_CONFIG_GLOBAL = path.join(scratch, 'gitconfig');
fs.writeFileSync(process.env.GIT_CONFIG_GLOBAL, '');
const git = (cwd, ...args) => execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trimEnd();
const local = name => import(pathToFileURL(path.join(checkout, name)).href);
let result = 'inconclusive';
try {
console.log(`Node ${process.version}; ${process.platform} ${process.arch}`);
if (typeof Bun !== 'undefined') console.log(`Bun ${Bun.version}`);
console.log(execFileSync('git', ['--version'], { encoding: 'utf8' }).trim());
console.log(`T3 Code ${git(checkout, 'rev-parse', 'HEAD')}`);
const repo = path.join(scratch, 'repository');
fs.mkdirSync(repo);
git(repo, 'init', '-q');
git(repo, 'config', 'user.name', 'Fixture');
git(repo, 'config', 'user.email', '[email protected]');
const require = createRequire(path.join(checkout, 'apps/server/package.json'));
const dep = name => import(require.resolve(name));
const Effect = await dep('effect/Effect');
const Layer = await dep('effect/Layer');
const NodeServices = await dep('@effect/platform-node/NodeServices');
const NodeCrypto = await dep('@effect/platform-node/NodeCrypto');
const GitDriver = await local('apps/server/src/vcs/GitVcsDriver.ts');
const VcsProcess = await local('apps/server/src/vcs/VcsProcess.ts');
const Config = await local('apps/server/src/config.ts');
const layer = Layer.mergeAll(GitDriver.layerVcs, GitDriver.layer).pipe(
Layer.provide(Config.layerTest(repo, path.join(scratch, 'state'))),
Layer.provideMerge(VcsProcess.layer),
Layer.provideMerge(Layer.mergeAll(NodeServices.layer, NodeCrypto.layer)),
);
fs.writeFileSync(path.join(repo, 'flagged.txt'), 'baseline\n');
fs.writeFileSync(path.join(repo, 'control.txt'), 'baseline\n');
git(repo, 'add', '.');
git(repo, 'commit', '-qm', 'baseline');
const worktree = path.join(scratch, 'worktree');
git(repo, 'worktree', 'add', '-q', '--detach', worktree, 'HEAD');
git(worktree, 'update-index', '--skip-worktree', 'flagged.txt');
await Effect.runPromise(Effect.gen(function* () {
const driver = yield* GitDriver.makeVcsDriverShape();
const checkpointRef = 'refs/t3/checkpoints/repro';
yield* driver.checkpoints.captureCheckpoint({ cwd: worktree, checkpointRef });
if (git(worktree, 'show', `${checkpointRef}:flagged.txt`) !== 'baseline') throw Error('capture');
fs.writeFileSync(path.join(worktree, 'flagged.txt'), 'later\n');
fs.writeFileSync(path.join(worktree, 'control.txt'), 'later\n');
const restored = yield* driver.checkpoints.restoreCheckpoint({ cwd: worktree, checkpointRef });
const control = fs.readFileSync(path.join(worktree, 'control.txt'), 'utf8') === 'baseline\n';
const flagged = fs.readFileSync(path.join(worktree, 'flagged.txt'), 'utf8') === 'baseline\n';
console.log(`restore returned: ${restored}; normal control restored: ${control}`);
console.log(`skip-worktree file restored: ${flagged}`);
if (!restored || !control) throw Error('control');
git(worktree, 'update-index', '--no-skip-worktree', 'flagged.txt');
yield* driver.checkpoints.restoreCheckpoint({ cwd: worktree, checkpointRef });
const workaround = fs.readFileSync(path.join(worktree, 'flagged.txt'), 'utf8') === 'baseline\n';
console.log(`restored after clearing flag: ${workaround}`);
if (!workaround) throw Error('workaround');
result = flagged ? 'expected behavior' : 'defect reproduced';
}).pipe(Effect.scoped, Effect.provide(layer)));
} catch (error) {
console.log(`Setup/control failure: ${error?._tag ?? error?.name ?? 'unknown'}`);
} finally {
fs.rmSync(scratch, { recursive: true, force: true });
}
console.log({ 'expected behavior': 'RESULT: expected behavior', 'defect reproduced': 'RESULT: defect reproduced', inconclusive: 'RESULT: inconclusive' }[result]);
process.exitCode = result === 'expected behavior' ? 0 : result === 'defect reproduced' ? 1 : 2;
Expected behavior
A successful file restore should return the workspace to the captured file contents, including tracked files whose index flags hide changes from ordinary Git commands. T3 Code documents that Revert files too puts the workspace back to its checkpoint and also undoes user edits: docs/user/composer.md#L172-L180. Git documents the skip-worktree interaction and its explicit override in the git-restore manual.
Actual behavior
Calling driver.checkpoints.restoreCheckpoint returns true and restores the normal control file to the checkpoint. The tracked flagged.txt, marked with git update-index --skip-worktree before capture and edited afterward, keeps later instead of the captured baseline. This reproduction uses a checkpoint whose flagged blob matches HEAD; a separate probe with different checkpoint and HEAD blobs did restore that file, so this report does not claim all flagged-file restores fail.
Impact
Minor bug or occasional failure
Impact: P2 - The file rollback silently leaves a subset of later edits in place. The observed case is a non-sparse worktree with a manually flagged tracked path whose captured blob matches HEAD; no data-loss incident was observed. A verified workaround is to clear that path’s skip-worktree bit with git update-index --no-skip-worktree -- flagged.txt before repeating restore.
Version or commit
main @ 0bb6eaa546c91915efbd3f092088c694f3949709. This report establishes the source-checkout behavior at that commit, not a separately installed release build.
Environment
Node v26.12.1; Git 2.56.0; macOS / darwin arm64. The script uses the checkout’s installed dependencies. Windows and Linux were not run.
Logs or stack traces
The capture and restore calls are real Git-driver operations, not copied shell implementations. Capture stores the expected baseline blob; the ordinary file restores; the flagged file does not; clearing the flag restores it. The restore command does not override skip-worktree bits in apps/server/src/vcs/GitVcsDriver.ts#L1396-L1411. The checkpoint service treats the returned true as success in apps/server/src/orchestration-v2/CheckpointService.ts#L526-L550.
Evidence provenance: observed. Local verification: reproduced. Reproduction completeness: complete at the production module boundary. The corresponding UI action was traced in source but was not exercised in a running client. Missing fact: NOT_ENOUGH_INFO — real-client behavior and other platforms require a separate UI run; no broader claim is made here.
restore returned: true; normal control restored: true
skip-worktree file restored: false
restored after clearing flag: true
RESULT: defect reproduced
Workaround
Verified: clear the affected file’s skip-worktree bit with git update-index --no-skip-worktree -- flagged.txt before repeating restore. This changes that index flag; the script confirms the contents then restore.
Restoration check
Repeat the script and require both files to match the captured contents when restore succeeds, without manually clearing flags. Preserve the ordinary-file control and verify that real sparse-checkout exclusions retain their intended behavior.
Expected source: the contract citations under Expected behavior. Failure source: the executed reproduction script and production source locations above. Evidence provenance: observed. Local verification: reproduced. Reproduction completeness: complete at the production module boundary. The running UI was not exercised.
References
- https://github.com/pingdotgg/t3code/issues/13741 — concerns checkpoint capture performance and handling flagged entries in the private capture index; this report concerns successful restore leaving later working-tree edits behind.
- Ngôn ngữ chính
- TypeScript
- Star
- 24.8k
- Fork
- 6.4k
- Merge trung bình
- 59 phút
- Pull request đã merge (30 ngày)
- 418
Chuẩn bị môi trường
Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của pingdotgg/t3code
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 66/100
Maintainer thường phản hồi trong vòng 1 ngày
-
via-triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
[Bug]: MCP App inline frames never go below 80px, so compact apps get blank space under themĐang mở
Độ khó 2/5 Nửa ngày Mức phù hợp với người mới 68/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Question card options show LaTeX as raw source, though the question text renders itCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
Maintainer thường phản hồi trong vòng 1 ngày
-
[Bug]: Settings inheritance popover shows raw slugs for branch naming, default permissions and response streamingCó thể đã có người làm @juliusmarminge đã nhận 1 ngày trước. Đang mởvouch:denounced
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của pingdotgg/t3code
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
nikoheikkila/nikoheikkila.fi#1131 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Resources: Update work of urumqiCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
railmapgen/rmp-gallery#4176 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
GraphQL custom headers duplicate differently cased default headersCó thể đã có người làm @yxr-2025 đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
elie222/rakazo#4625 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
Maintainer thường phản hồi trong vòng 1 ngày
-
[New Project]: PacaĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
github/forgoodfirstissue#574 ·