github / github/copilot-cli

TaskShellProgress.recentOutput is a required string, so the runtime substitutes the display literal (no output yet) when a shell task has produced nothing

Đang mở
#4,675 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

area:tools
Ngôn ngữ chính
Shell
Star
11.2k
Fork
1.9k
Merge trung bình
14 giờ 16 phút
Pull request đã merge (30 ngày)
6

Mô tả

Describe the bug

TaskShellProgress.recentOutput is declared required and non-nullable, so
there is no legal way for the runtime to answer "shell task, running, nothing
emitted yet."
It resolves the conflict by substituting a human-readable
placeholder into the data field:

// sdk/index.js @ 974738, identical copy in app.js @ 2324928
async getBackgroundTaskProgress(e) {
  if (e.type === "shell") {
    let r = this.getSessionShellContext()?.getShellTaskProgress(e.id);
    return { type: "shell", recentOutput: r?.recentOutput ?? "(no output yet)", pid: r?.pid };
  }
  ...
}

The contrast with the neighbouring field is the whole bug: pid is optional,
so when it is unknown it is simply absent — correct and unambiguous. In the
same response recentOutput cannot be absent, so absence is rendered as
content.

This is not a cosmetic default. A programmatic client cannot distinguish:

  1. the task has produced no output yet, from
  2. the task printed the characters (no output yet).

Both arrive as the same bytes in the same required field. The API states
something false about the process, and the falsehood is undetectable at the
call site.

null does not help: the dispatcher reserves it for an unlisted id, so a
listed-but-silent task always reaches the sentinel branch —

// app.js @ 2213353
case "tasks_get_progress": {
  let o = this.getBackgroundTasks().find(s => s.id === wo(n, "taskId", "id"));
  return { progress: o ? await this.getBackgroundTaskProgress(o) : null }
}
Affected version

@github/copilot 1.0.80 (@github/copilot-sdk 1.0.11), win32 x64.

Current generated contract, copilot-sdk/dist/generated/rpc.d.ts:

/** @experimental */
export interface TaskShellProgress {
    type: "shell";
    recentOutput: string;   // required — cannot express "nothing yet"
    pid?: number;           // optional — correctly absent when unknown
}
Steps to reproduce the behavior

Poll tasks.getProgress against a shell task that stays silent after start —
the window is easy to miss with a task that prints immediately, so force it:

  1. Start an attached (sync) shell task whose command emits nothing for
    ~20s, then prints steadily:

    node -e "setTimeout(()=>{let i=0;const t=setInterval(()=>{console.log(`line ${++i}`);if(i>60)clearInterval(t)},300)},20000)"
    
  2. Poll tasks.getProgress({ id }) at ~250 ms from the moment the task is
    listed.

  3. Observe the sentinel for the whole pre-first-output window. Verbatim,
    17 consecutive polls over ~4.1 s (att/exec from tasks.list):

    {"ms":78782,"id":"node-task","status":"running","att":"attached","exec":"sync","recentOutput":"(no output yet)"}
    {"ms":82894,"id":"node-task","status":"running","att":"attached","exec":"sync","pid":28012,"recentOutput":"(no output yet)"}
    

    Note pid behaving correctly across those same polls — absent at 78782,
    present at 82894 — while recentOutput cannot be.

  4. Once the command starts printing, recentOutput carries real output
    normally.

Expected behavior

recentOutput should be able to express absence, so a client can render it
honestly. Either:

  • make it optional (recentOutput?: string) and omit it — consistent with
    how pid already handles the unknown case, and the preferred shape; or
  • keep it required and return the empty string, reserving all non-empty
    values for real process output.

Either way the invariant worth stating explicitly in the schema is: every byte
in recentOutput was written by the task.
Any client-side presentation of
"nothing yet" belongs to the client.

Additional context

Why a placeholder is worse than absence here. Downstream this does not stay
cosmetic. In our host the value is non-empty, so it passes a truthiness guard
and is accumulated into the per-task buffer used to content-match a task to its
largeOutput file. No real log contains (no output yet), so the match can
never succeed and the task is permanently locked out of its own complete log —
a silent, unrecoverable failure caused entirely by a field that was supposed to
mean "no data". An empty string, or an absent field, would have been handled
correctly by code that already existed.

Relationship to #4630 — related but neither subsumes the other. #4630 asks
for largeOutputFilePath / largeOutputTotalBytes on this same interface,
because recentOutput is a lossy window. That is a fidelity gap in real
output; this is a correctness bug where the field's contents are not output at
all. They also do not overlap in time: #4630 notes the largeOutput file
appears only after a size threshold (~17s in its probe), which is precisely the
window in which this sentinel is served — so landing #4630 would leave this
unfixed, and fixing this leaves #4630's lossiness untouched. Separate fixes:
this one is a nullability change to an existing field, #4630 is a schema
addition currently blocked by "additionalProperties": false.

Scope. Observed on an attached (sync) task. The producing branch is not
conditioned on attachment, so detached tasks should behave the same, but I only
measured the attached case. The pre-first-output window is the trigger; a task
that prints immediately closes it too fast to notice, which is likely why this
has gone unreported.

Correction to an earlier reading. An attached task is not permanently
without a progress record — in the same session a different attached task
returned real recentOutput throughout. The sentinel is a startup-window state,
not an attachment-mode one.

  • Discovered while polling from a GUI host embedding the SDK in-process.
  • Evidence read from the shipped bundle at the offsets cited above, not inferred
    from behaviour alone.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu với khai báo TaskShellProgress trong copilot-sdk/dist/generated/rpc.d.ts, sau đó kiểm tra các triển khai getBackgroundTaskProgress trong sdk/index.js và app.js. Chạy bản tái hiện việc polling tác vụ được đính kèm ở chế độ im lặng được mô tả trong issue và xác minh rằng schema và response biểu diễn trạng thái trước khi có output mà không nhầm lẫn trạng thái đó với output thực của tác vụ.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
javascript, node.js, typescript
Lĩnh vực
api, backend-api-design
Loại issue
Lỗi
Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
67/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.