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
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-onfail from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.