TaskShellProgress.recentOutput is a required string, so the runtime substitutes the display literal (no output yet) when a shell task has produced nothing
Nadie ha tomado este issue todavía.
- Lenguaje dominante
- Shell
- Estrellas
- 11.2k
- Forks
- 1.9k
- Merge medio
- 14 h 16 min
- PR fusionados (30 d)
- 6
Descripción
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:
- the task has produced no output yet, from
- 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:
-
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)" -
Poll
tasks.getProgress({ id })at ~250 ms from the moment the task is
listed. -
Observe the sentinel for the whole pre-first-output window. Verbatim,
17 consecutive polls over ~4.1 s (att/execfromtasks.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
pidbehaving correctly across those same polls — absent at 78782,
present at 82894 — whilerecentOutputcannot be. -
Once the command starts printing,
recentOutputcarries 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
howpidalready 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.
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Línea de trabajo
Comienza con la declaración de TaskShellProgress en copilot-sdk/dist/generated/rpc.d.ts y, después, inspecciona las implementaciones de getBackgroundTaskProgress en sdk/index.js y app.js. Ejecuta la reproducción silenciosa del sondeo de la tarea adjunta descrita en el issue y verifica que el esquema y la respuesta representen el estado previo a la salida sin confundirlo con una salida real de la tarea.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- javascript, node.js, typescript
- Área
- api, backend-api-design
- Tipo de issue
- Error
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Estado de actividad
- Activo
- Claridad
- Bien especificado
- Aptitud para principiantes
- 67/100