Skip to content
dsh.fish
Bundle

dsh-onfail

CI failed. Stay in DSH. Click the card, start the fix. / CI 挂了也别离开 DSH:侧栏一张失败卡,点一下带 log 开修。

Source
ZZZjf13960
stars
1 stars
License
MIT
Updated
Updated yesterday

Readme

# dsh-onfail

CI failed. Stay in DSH. Click the card, start the fix.
CI 挂了也别离开 DSH:侧栏一张失败卡,点一下带 log 开修。

DeepSeek Harness plugin: poll **this workspace's** GitHub repository Actions (current branch, or open PR head), surface **new** failed checks as **sidebar cards** (job name, SHA, useful log excerpt), and **continue the current session** with that failure context.

Tested on **dsh 0.1.1-rc.2** (`package.json` `dsh.peer` / `engines.dsh`).

v0.2 is **polling only**. No webhooks.

## Install

Requires a DSH web profile. From any directory:

```bash
dsh plugin --profile web add github:ZZZjf13960/dsh-onfail
```

This package ships **committed JavaScript** (`dsh/index.js` + `dsh/client.js`) on `main`, so the GitHub specifier is loadable without a `prepare` build or pnpm `allowBuilds` gate.

Then start (or restart) the web UI:

```bash
dsh --profile web
```

The plugin mounts as package name `dsh-onfail` via `cordis.patch.yml`. Settings, then Plugins, should show an **Onfail (GitHub Actions)** card. A **CI** control appears in the sidebar footer; failed checks also render as overlay cards.

## Remove

Either form drops the profile dependency and the bundle layer:

```bash
dsh plugin --profile web remove github:ZZZjf13960/dsh-onfail
```

```bash
dsh plugin --profile web remove dsh-onfail
```

Restart the web UI so the host process unloads the routes and the browser half.

## Configure

| Field | Default | Where |
| ----- | ------- | ----- |
Default scope is **auto**: current workspace `git remote get-url origin` (owner/repo) and `git rev-parse --abbrev-ref HEAD` (branch). If an open PR exists for that branch (`gh pr view` or REST), the plugin follows the PR head. Manual `repo` in settings still overrides auto-detect.

| Field | Default | Where |
| ----- | ------- | ----- |
| `repo` | auto from git origin | Plugin settings card, or `config.repo` (`owner/name`). Empty means auto. |
| `token` | empty | Plugin settings card (never echoed back). Prefer `gh auth login`. Also `DSH_ONFAIL_TOKEN` then `GITHUB_TOKEN` |
| `pollIntervalMs` | `60000` | Settings card or loader config (~60s) |
| `fixture` | `false` | Settings checkbox, loader config, or `DSH_ONFAIL_FIXTURE=1` |
| `logTailChars` | `4000` | Fetch cap; cards/prompts store a short useful excerpt |

Token resolution order: plugin config token → `gh auth token` / existing `gh` login → `DSH_ONFAIL_TOKEN` → `GITHUB_TOKEN`. If `gh` cannot read Actions, fall back to the env/config token path (missing `gh` must not mean missing cards). Missing both is a readable error; the harness does not crash. Nothing is hardcoded.

A classic repo PAT (or GitHub App installation token) needs Actions read on that repository. Fine-grained PATs need **Actions: Read**. With `gh auth login`, a second PAT is not required.

Loader override example (`$DSH_HOME/profiles/web/cordis.patch.yml`):

```yaml
- id: dsh-onfail
  config:
    repo: your-org/your-repo   # optional; empty = auto from git origin
    pollIntervalMs: 60000
    fixture: false
```

## Polling

Every `pollIntervalMs` the host half:

1. Detects owner/repo + branch (and open PR head, if any) from the workspace cwd
2. `GET /repos/{owner}/{repo}/actions/runs?status=completed&branch={branch}`
3. Filters to that branch / PR head SHA — not every historical red on `main`
4. For failed jobs, fetches logs and stores failed step + useful error lines (redacted)
5. Emits a card only if this job id + SHA has not already been shown (persisted under `$DSH_HOME/onfail-shown.json`). A rerun with a new job id may show again. Polling the same completed failed job does not restack.

There are **no webhooks**. The browser half polls `GET /dsh-onfail/state` every few seconds and renders cards.

## Click to continue the current session

Clicking a card calls `session.prompt(..., 'queue')` on the **current** session (`sessions.list.current` / `sessions.binding` / `sessions.active`). It does not create a session, does not open a new session id, and does not steal focus. If there is no current session, the card shows a readable `role=status` error — that is not success. Composer paste is not success.

## Fixture mode

Tests and a live UI walkthrough can produce a card **without a GitHub token**:


- Settings, Onfail, enable **Fixture mode**, or
- `DSH_ONFAIL_FIXTURE=1`, or
- loader `config.fixture: true`

This loads `fixtures/failed-run.json` (`acme/widget`, job `unit-tests`, SHA `aaaabbb`, a failing test log tail). Fixture mode treats that payload as a new failure so a card still appears.

```bash
node --test
```

## Acceptance checks

1. **Install from GitHub on main.** Manifest tests assert `dsh.bundle.patch` points at `cordis.patch.yml` that inserts package `dsh-onfail`, plus `exports["./client"]` and `dsh.client`. JS is committed on `main`. Verifier still runs `dsh plugin --profile web add github:ZZZjf13960/dsh-onfail`.

2. **Failed Actions checks become sidebar cards** (job, SHA, log excerpt) for the **current branch / open PR**, **new failures only**. Fixture and the poll mapper produce that card shape. The client registers `sidebar.footer.action` and `shell.overlay`. Verifier still confirms the CI footer / overlay cards in DSH Web.

3. **Clicking a card continues the current session** with repo, job, SHA, failed step, and a short useful log. The click path is `session.prompt(..., 'queue')` on the current session. A missing current session or rejected prompt shows a `role=status` error on the card. Composer paste is not a session. Verifier still clicks a card in the live UI.

4. **Missing token: readable error, harness does not crash or hang.** `apply()` with no token (and no `gh` login) sets `error` and still registers routes; it never throws. Verifier still opens the plugin UI with empty token/repo.

5. **Remove drops the profile entry.** Documented; `dsh plugin remove` reconciles bundles by installed state. Verifier still runs remove and checks `dsh.profile.bundles`.

## Layout

```
dsh-onfail/
  package.json           # dsh.bundle + dsh.client + dsh.peer + exports
  .github/workflows/test.yml
  cordis.patch.yml       # insert id/name: dsh-onfail
  fixtures/failed-run.json
  dsh/index.js           # host apply
  dsh/plugin.js          # poll loop + /dsh-onfail/* routes
  dsh/client.js          # lazy CJS browser half
  dsh/config.js poll.js cards.js http.js scope.js shown.js log.js
  test/*.test.js

```

## License

MIT

Install

dsh plugin --profile web add github:ZZZjf13960/dsh-onfail

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source