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:
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:
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:
dsh plugin --profile web remove github:ZZZjf13960/dsh-onfail
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):
- 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:
- Detects owner/repo + branch (and open PR head, if any) from the workspace cwd
GET /repos/{owner}/{repo}/actions/runs?status=completed&branch={branch}- Filters to that branch / PR head SHA — not every historical red on
main - For failed jobs, fetches logs and stores failed step + useful error lines (redacted)
- 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.
node --test
Acceptance checks
Install from GitHub on main. Manifest tests assert
dsh.bundle.patchpoints atcordis.patch.ymlthat inserts packagedsh-onfail, plusexports["./client"]anddsh.client. JS is committed onmain. Verifier still runsdsh plugin --profile web add github:ZZZjf13960/dsh-onfail.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.actionandshell.overlay. Verifier still confirms the CI footer / overlay cards in DSH Web.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 arole=statuserror on the card. Composer paste is not a session. Verifier still clicks a card in the live UI.Missing token: readable error, harness does not crash or hang.
apply()with no token (and noghlogin) setserrorand still registers routes; it never throws. Verifier still opens the plugin UI with empty token/repo.Remove drops the profile entry. Documented;
dsh plugin removereconciles bundles by installed state. Verifier still runs remove and checksdsh.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
No comments yet. Be the first to write one.