Skip to content

panel: control bytes in the HELD and FAILED panes, and four sandbox-launch residuals #367

Description

@edgehero

Filed under PR #366's round cap: three gate rounds on issue #337 items 2, 3 and 4, then merge with what survives recorded here. None of these is a regression; each is either outside what #337 named or a bound that cannot be closed from where the panel stands.

1. The HELD and FAILED panes render their own ids raw

#337 item 3 named RUN_DETAIL. That pane and the LIST both hold the property now, and the same file does not:

  • heldListRow prints r.target, r.label and r.jobId unscrubbed, and heldListHints prints pendingCancel.target ?? pendingCancel.jobId into the armed question.
  • failedRow prints r.jobId and r.queue unscrubbed. The projection scrubs failedReason and takes jobId raw on the very next key, so the one field with a belt sits beside two without one.

A review pass drove a record whose job id carried a carriage return and a clear-screen sequence, and both panes rendered them into the row and into the armed cancel question.

The fix is the same one-line shape used in runRow and renderRunDetail. It was left out because the issue named one pane and this round already widened to a second; extending again without the issue asking is scope the PR should not take silently.

2. Two genuine pre-start refusals exit 1 and stay silent

The exit message fires for 125, 126 and 127, which is what a container runtime uses to refuse before the container runs. Measured against docker 27.4.0, two refusals land outside that set: -t with no TTY (the client refuses, no container is created), and a DOCKER_CONTEXT naming a context that does not resolve ("unable to resolve docker endpoint"). Both exit 1.

The second is the sharp one, because the message was extended specifically to name DOCKER_HOST as a cause, and the DOCKER_CONTEXT form of exactly that mistake says nothing. Widening to 1 is not the answer: 1 is the commonest shell exit there is.

3. A healthy shell whose last command was a typo buys a 2.5 s pause

The sandbox runs --entrypoint bash -i, so docker run returns bash's status, and bash returns 127 for command not found. The message is worded so it is true either way ("if no shell opened, ..."), but pauseForMessage is a fixed timer, so a typo at the end of a debugging session costs one pause on the way back to the panel. That is the stated trade for not letting a genuine exec bash failed go silent again.

Separating the two properly needs a docker read after the exit (whether a container by that name exists, or a --cidfile), which is a second round trip inside a key handler. Recorded in OQ-038 as one more thing the panel cannot settle without docker.

4. readSandboxInfo can never set running, so RUN_DETAIL's "running" branch is unreachable

renderRunDetail renders sandbox.running ? "running" : "retained", and the reader returns only retained, pinned, expiresIn and egress. The function's docblock says asking docker is deliberately out of scope, which is right, but then the branch should go or say it is reserved. The one state it would show is the one that would warn an operator before openSandbox's already-running refusal.

5. The gate asks three manifest-only refusals and there is a fourth

sandboxSyncRefusal carries the venue, the image and the workspace. decideSandboxJobUser can also refuse a linux run from manifest.jobUser alone (issue #341), and the panel does not ask it, so a run with a malformed job-user stamp is still offered b and given two lines of egress detail. Folding it in changes what the CLI refuses and when, which is a decision of its own rather than a tidy-up.

6. A proxy name with leading or trailing spaces is shown but not communicated

PI_EGRESS_PROXY set to a name with surrounding spaces is rendered untrimmed, deliberately, because openSandbox does not trim either and the line's whole point is to name what b will look for. But a reader sees the bare name: the leading space is indistinguishable from the separator and the trailing one is invisible. The blank case gets a sentence; this one gets nothing the eye can catch.

Measurements worth keeping

  • readSandboxInfo costs 13-20 microseconds per call (manifest read plus one existsSync), and runs on RUN_DETAIL entry and on every left/right between runs. Immaterial in a key handler.
  • The gate has zero drift against resolveSandbox across 11 manifest shapes: same conditions, same order, nothing refused that resolveSandbox accepts.
  • styler.cell clips to the budget minus the ellipsis, so a hand-cap at the same column is byte-identical at every width the pane can take, and hardcoding the glyph regresses PI_DISPATCH_ASCII.
  • The unframed degrade (a width under 8, or one that is not a finite number) bypasses both frame builders, so anything scrubbed only inside frame/box is unscrubbed there.
  • stripAnsi swallows everything between an OSC-8 opener and its terminating bell, so an assertion on stripped text cannot see a clear-screen hidden in a hyperlink payload. Strip only the styler's own SGR when testing this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions