workspace.commit: a merge conflict with a few hundred hunks is refused by the control-record encoder instead of truncated
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ó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 82/100
Hướng nghiên cứu
Bắt đầu tại put_report trong engine/crates/layerfs-bridge/src/control_merge.rs:212-227 và so sánh cách đếm hunk của nó với scan tích lũy được dùng bởi put_lines và put_lww lân cận, đồng thời kiểm tra giới hạn bản ghi trong layerfs-bridge/src/wire.rs. Thêm bài kiểm tra chỉ encoder được cung cấp qua API công khai dưới engine/crates/layerfs-bridge/tests/ và chạy cargo test -p layerfs-bridge --all-targets trước và sau. Hoàn thành nghĩa là một báo cáo 600 hunk được mã hóa trong giới hạn wire, giải mã trở lại với truncated = true, và tuyên bố về blocks trong tài liệu hoặc được sửa hoặc được để lại với một quyết định đã ghi chú.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Environment
- LayerFS
1.1.0, checkout35d36575c1eb; Rust 1.99.1, macOS arm64 - The bug is still present on
mainandlayerfs_v2_0_0as of 2026-11-15
(engine/crates/layerfs-bridge/src/control_merge.rsis byte-identical to the
checkout above on both branches) - Reproduced on the encoder directly; no daemon, Docker or Store involved
Summary
A Workspace Commit whose Branch head moved returns a Conflict reply carrying every
conflicting hunk. The control wire holds one reply in at most u16::MAX - 16
bytes (layerfs-bridge/src/wire.rs:5-18) and the encoder is supposed to truncate
what does not fit, saying so with truncated (control_merge.rs module docs:
"conflicts and hunk lines are written while they fit and truncated says that some
were left out").
The hunk list is never bounded. put_report counts hunks with
let hunks = conflict.hunks.iter().take_while(|_| fits(out, 64)).count();
(engine/crates/layerfs-bridge/src/control_merge.rs:223). The predicate ignores
its element and out does not change during take_while, so the result is
constant: every hunk is kept whenever any hunk fits. The write loop then emits all
of them, Writer::put returns ControlError("control record limit") once the
record passes the limit, and answer_call
(engine/crates/layerfs-daemon/src/control/serve.rs:97-132) turns that into
ServeCause::Protocol and closes the send half of the connection.
The merge has already run: the conflicted Workspace has marker files installed
("A conflicted Workspace: its local base is the merge with markers",
store/commit_merge.rs). The caller gets a lost connection instead of the conflict
report, and never learns which paths conflicted.
Repro
A unit test on the encoder alone, using only public API, is enough. Drop this in
engine/crates/layerfs-bridge/tests/ and run cargo test -p layerfs-bridge:
use layerfs_bridge::control::{Answer, ConflictReport, Reply};
use layerfs_content::merge::{Conflict, ConflictKind, Hunk};
#[test]
fn large_conflict_report_encodes() {
let hunk = |i: usize| Hunk {
line: i * 4 + 1,
ours: (0..3).map(|k| format!("a{}", "x".repeat(39 - (i + k) % 7))).collect(),
theirs: (0..3).map(|k| format!("b{}", "x".repeat(39 - (i + k) % 7))).collect(),
};
let report = ConflictReport {
head: None,
base: None,
markers: false,
conflicts: vec![Conflict {
path: "/project/generated.config".into(),
kind: ConflictKind::Content,
hunks: (0..600).map(hunk).collect(),
}],
lww: vec![],
written: vec![],
truncated: false,
};
Answer { id: 7, reply: Reply::Conflict(Box::new(report)) }
.encode()
.expect("a valid conflict report must encode");
}
Measured on 35d3657 with the same 40-byte lines, 3 per side:
| hunks | encoded | result |
|---|---|---|
| 20 | 4,890 B | ok |
| 200 | 48,466 B | ok |
| 300 | 64,740 B | ok |
| 400 | — | refused: control record limit |
| 600 | — | refused |
The same happens with the more realistic shape of many files: 60 files × 10
hunks each (600 hunks total) is refused.
Input size is well inside the merge's own bound: MAXIMUM_TEXT_BYTES = 1 MiB
(core/crates/layerfs-content/src/compare/text.rs:12), and 400 hunks × 6 lines × 40 B ≈ 96 KiB of text. There is no cap on conflicts or hunks in
layerfs-content/src/merge.
Root cause
engine/crates/layerfs-bridge/src/control_merge.rs:212-227. Every other bounded
list in this file (put_lines, put_lww, the conflicts list) uses a cumulative
scan and stops at the first entry that does not fit; the hunk count alone uses a
constant predicate.
Impact
- The agent-platform case the project targets — many Agents editing large files
and merging often — produces exactly this shape: a merge conflicting in a few
dozen files, or one large generated file with several hundred conflicting
regions. - The Commit side effects have already happened (markers installed); the call
reports a transport/protocol failure. A retry then hits the marker refusal
instead, so the original conflict report is lost. - The documented contract is truncation, and
conflictsTruncatedis the flag the
SDK already exposes (layerfs-api/src/sandbox/values.rs), so callers are
prepared for a truncated report.
Proposed fix
Replace the constant predicate with the cumulative budget the neighbouring
encoders use:
let hunks = conflict
.hunks
.iter()
.scan(0usize, |size, hunk| {
*size += 8 + lines_len(&hunk.ours) + lines_len(&hunk.theirs);
Some(*size)
})
.take_while(|size| fits(out, *size))
.count();
with lines_len summing each line's clipped length plus its two-byte prefix
(the 8 covers the hunk's line number and both list counts). Result with the
patch, same inputs:
| input | encoded | kept | truncated |
|---|---|---|---|
| 400 / 600 / 1200 hunks | 64,426 B | 266 hunks | true |
| 60 files × 10 hunks | 65,226 B | 261 hunks | true |
cargo test -p layerfs-bridge --all-targets passes with the patch, and the
encoded reply decodes back to a Conflict with truncated = true.
One follow-up to decide with the fix: api/docs/agent.md says "Each conflict
keeps its original blocks count", but once the wire truncates, the SDK's
blocks becomes the kept count on the workspace.commit path (the wire does
not carry the original). Either carry the original count or amend that sentence.
Overlap check
Searched open and closed issues and PRs for conflict, truncat,
control record, hunks, merge, written twice, Workspace capacity.
Two adjacent items, both different defects:
- #355 F-041: the merge report drops its
truncatedflag for the 4,096 written
paths instore-service/src/store/commit_merge.rs. This issue is about the
control encoder refusing an over-limit reply; the flag already exists here. - #517: the bounded diff fallback silently merges away a change over ~1023 lines.
Different layer and different symptom.
No issue or PR mentions put_report, hunk truncation, or control record limit.
Verification standard
For any conflict report, Answer::encode either returns a record within
wire::LIMIT whose decoded truncated flag is true when anything was omitted,
or the input must not be valid. It must not return an error for a report the
merge can produce.
- Ngôn ngữ chính
- Rust
- Star
- 2.1k
- Fork
- 110
- Merge trung bình
- 23 giờ 52 phút
- Pull request đã merge (30 ngày)
- 138
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
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 Ephemeral-AI-Lab/layerfs
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
Ephemeral-AI-Lab/layerfs#643 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
`branch.publish` accepts a stale base and overwrites `main` (lost update)Có thể đã có người làm @mengmengmengqiang đã nhận hôm nay. Đang mởapi/calls bug core/history
Ephemeral-AI-Lab/layerfs#691 · 1 bình luận · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 8/100
Ephemeral-AI-Lab/layerfs#684 · 10 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
LayerFS Paper Write in PublicĐang mởdocs
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 15/100
Ephemeral-AI-Lab/layerfs#683 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
Ephemeral-AI-Lab/layerfs#676 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của Ephemeral-AI-Lab/layerfs
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
pop-os/cosmic-settings#2296 ·
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 68/100
Maintainer thường phản hồi trong vòng 2 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
jonathanong/no-mistakes#3431 · 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 62/100
Eyevinn/open-live-ingest#36 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày