Skip to content
Back to skills

Devin Runtime

BSecurity

Contract of the devin bridge script — devin-run.sh subcommands and options, background jobs, named sessions, permission modes, the OS sandbox, workspace trust, job states, exit codes, failure modes. Internal reference attached to the devin-runner subagent; read it when any devin-* skill needs the exact call.

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 7, 2026
toolspythonrustgoshellbashsqlgitdatabase

Works with

  • terminal
  • cli

Security analysis

B75/100
  • criticalPipes output to a shell interpreter

Pro shows the line behind each finding and how to fix it

Scanned October 7, 2026

npx -y skills add dmitry-fomin/listik --skill devin-runtime --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Devin Runtime?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Devin Runtime
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/dmitry-fomin-devin-runtime/badge)](https://www.skillsdirectory.com/skills/dmitry-fomin-devin-runtime)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: devin-runtime
description: "Contract of the devin bridge script — devin-run.sh subcommands and options, background jobs, named sessions, permission modes, the OS sandbox, workspace trust, job states, exit codes, failure modes. Internal reference attached to the devin-runner subagent; read it when any devin-* skill needs the exact call."
user-invocable: false
allowed-tools: Bash(${CLAUDE_PLUGIN_ROOT}/scripts/devin-run.sh *)
---

The only way to call devin is `${CLAUDE_PLUGIN_ROOT}/scripts/devin-run.sh`. Calling bare
`devin` loses the default permission mode, job bookkeeping, session names and the
prompt-file handling.

**Invariant:** stdout of `run`/`resume` (without `--background`) and of `result` is exactly
devin's final answer and nothing else. Everything else goes to stderr, so text on stderr is
always a problem signal.

## Commands

```bash
${CLAUDE_PLUGIN_ROOT}/scripts/devin-run.sh run --background --session "<name>" --label "<topic>" <<'TASK'
<task text>
TASK
```

| Subcommand | Purpose |
| --- | --- |
| `run` | new session; prompt on stdin. With `--background` stdout is a job-id, without it the answer |
| `resume <name\|job-id\|devin-session-id>` | continue an existing session; prompt on stdin, same flags as `run` |
| `check [--json] [--no-probe] [--probe-timeout <s>]` | readiness: binary, version, python3, session database, model catalogue, and a real probe run |
| `status [--json] [--all] [--running] [job-id]` | no argument — jobs of the current cwd subtree; `--all` — every job on the machine; with an id — one job card |
| `result <job-id> [--wait [s]]` | fetch the answer; `--wait` waits the given seconds (default 300) |
| `logs <job-id> [--tail N]` | job lifecycle plus the turns of the devin session: which tools ran, what came back |
| `cancel <job-id\|--all>` | kill the job and its whole process tree (`kill` is a synonym) |
| `clean [--older-than <days>] [--all]` | drop finished jobs; never touches running jobs or devin's own sessions |
| `sessions [--json]` | sessions known by name: name, devin session id, model, last update, directory |
| `transcript [job-id] [--session <name>]` | print the devin conversation as JSONL — what actually happened |

Every subcommand takes `-h`.

Options for `run`/`resume`:

| Option | Default | Meaning |
| --- | --- | --- |
| `--background` | off | detach, return a job-id instead of the answer |
| `--label <text>` | none | short tag; the only way jobs differ on sight in `status` |
| `--session <name>` | none | `run` creates it, `resume` finds it |
| `--permission <read\|bash\|write>` | `read` | permission mode in one flag |
| `--write` / `--bash` | off | aliases for `--permission write` / `--permission bash`, kept for Listik routes and pipeline presets |
| `--thinking <medium\|high\|max>` | `medium` | effort level = model `swe-2-<level>`; this list only |
| `--channel <swe>` | `swe` | the only channel; the flag exists so presets can pass it |
| `--sandbox` | off | devin's OS sandbox for the exec tool (macOS seatbelt / Linux bwrap) |
| `--trust-workspace` | always on | kept for compatibility: `--respect-workspace-trust false` is always passed, on `run` and `resume` alike |
| `--cwd <dir>` | current | working directory of the run |
| `--timeout <s>` | 540 foreground, 7200 background | `0` removes the limit |

## Non-obvious rules

- **Background is the default choice.** A devin run blocks until the agent finishes, and a
  subsystem sweep easily outlives the 600 s Bash-call ceiling. Foreground is for questions
  answered inside the current turn.
- **Quote the heredoc marker** (`<<'TASK'`): task text is almost always code, and an
  unquoted marker lets the shell expand `$` and backticks. The prompt reaches devin as a
  file (`--prompt-file`), so multiline text with code survives intact and has no size limit
  here.
- **One channel, one decision to make: effort.** `swe` is SWE-2, free, 262K context.
  `--thinking` picks `swe-2-medium` (default), `swe-2-high` or `swe-2-max`. Raise it only
  when the human asked or a pipeline preset requires it.
- **`--permission bash` is wider on devin than on the other bridges.** It maps to devin's
  `smart` mode, which also auto-approves edits it judges safe — devin has no "commands but
  no edits" mode. If the working tree must stay untouched, stay on `read`.
- **The OS sandbox is off by default.** `--sandbox` confines the exec tool to the workspace;
  it is a real boundary, not a formality, and it breaks tasks that legitimately write
  outside the working directory. Turn it on deliberately.
- **Secrets are a prompt-side concern.** devin opens files on its own, so scope the task to
  the files it needs and explicitly forbid `.env`, `*.key`, `*.pem`, `credentials.json`.
- **Parallel runs are supported**, including in one working directory — separate sessions,
  separate job directories, no shared lock. Exception: two `--permission write` runs in the
  same directory overwrite each other's edits, and two runs into one session interleave
  their transcripts.
- **Don't poll in a foreground Bash call.** Wait with one backgrounded call instead:

  ```bash
  until ! ${CLAUDE_PLUGIN_ROOT}/scripts/devin-run.sh status <job-id> | grep -q '^actual_status=running'; do sleep 20; done
  ```

  Its completion notification is the "devin finished" signal.

## Job states

| Status | Meaning |
| --- | --- |
| `running` | process alive, devin working |
| `completed` | answer ready, fetch with `result` |
| `timeout` | hit its limit; `result` still returns the partial answer |
| `canceled` | killed via `cancel` |
| `failed` | devin exited with an error (cause in `logs`), or exited 0 after rejecting a tool call; the cause is in `error` of `status` and on stderr of `result` |
| `orphaned` | process gone, outcome never written: reboot or `kill -9`. No answer is coming |

`status <id>` prints the status twice: `status=` is what the worker recorded,
`actual_status=` corrects it for process liveness. **Trust `actual_status`.** devin exiting
is not yet `completed` — the worker still has to record the outcome, and during those
seconds the job is honestly `running`.

## Sessions

- A devin session id is a human-readable slug (`lilac-helenium`), not a uuid. `--session
  <name>` maps your own name onto it; `sessions` shows both.
- `resume` accepts a session name, a job-id, or a bare devin session id — the last one lets
  you continue a session a human started interactively.
- `resume` inherits the directory, permission mode, effort level and sandbox flag of the
  original run unless flags override them. The model of a resumed session is kept by devin
  itself unless `--thinking` changes it.
- One name = one session: `run --session <taken name>` is exit 2, and so is `resume` on a
  name that does not exist — fall back to a fresh `run` with the current text.
- Job state (prompt, events, answer) is stored in plaintext in the state directory and
  never expires; `clean` removes it. devin's own sessions live in its sqlite database and
  `clean` leaves them alone (`devin rm <session-id>` deletes one).

## How a run is wired

- devin reads `AGENTS.md`/`CLAUDE.md` in the working directory itself — never restate
  project rules in the task.
- Each run is one `devin --model swe-2-… --permission-mode … --prompt-file … -p`, started
  in the working directory: **devin has no working-directory flag**, so `--cwd` is a `cd`.
- devin has no background mode of its own; the job-id, the worker and the process tree are
  entirely the script's.
- `devin list` is an interactive TUI and unusable from a script. The bridge reads session
  ids from devin's own sqlite database (`~/.local/share/devin/cli/sessions.db`,
  read-only) — that is also where `logs` and `transcript` get the turns.
- `DEVIN_CLAUDE_BIN` points at the binary, `DEVIN_CLAUDE_STATE_DIR` at the state directory,
  `DEVIN_CLAUDE_SESSIONS_DB` at devin's session database, `DEVIN_CLAUDE_DEFAULT_THINKING`
  moves the default effort level.

## Permission modes on devin

devin's own ladder is `auto` ⊂ `accept-edits` ⊂ `smart` ⊂ `dangerous`, and it puts edits
*before* commands — the opposite of ours. The bridge maps:

| `--permission` | devin `--permission-mode` | Effect |
| --- | --- | --- |
| `read` | `auto` | only read-only tools are auto-approved |
| `bash` | `smart` | commands **and** edits a fast model judges safe |
| `write` | `dangerous` | everything auto-approved |

In non-interactive (`-p`) mode there is nobody to confirm a tool, so a tool that is not
auto-approved is rejected outright: devin warns on stderr and **ends the turn** with
exit 0; stdout may still hold interim text, which is not the answer. The bridge reports
that as exit 6 (background: status `failed`) with the cause
`devin rejected a tool call in --permission <perm>: the answer is partial; …`, not as a
silent success; any partial text is still printed on stdout. An empty answer with no
rejection is still exit 6 "empty answer".

## Exit codes

| Code | Meaning | Action |
| --- | --- | --- |
| 0 | success: answer, job-id or report on stdout | pass it through verbatim |
| 1 | `check`: devin not ready; `status`/`sessions`: no records | an answer, not a failure |
| 2 | bad call: missing binary, empty prompt, bad option, unknown job-id, missing or taken session name | fix the command, or fall back to `run` |
| 5 | background job still running | wait and retry `result` |
| 6 | timeout, cancel, non-zero devin exit, rejected tool call (partial text may be on stdout) or empty answer | check `logs`, then `check` |

## Failure modes

| Symptom | Cause / action |
| --- | --- |
| Bash call cut at 600 s | run was started in foreground; restart with `--background` |
| `devin not found in PATH` | not installed, or installed after the session started — new terminal or `DEVIN_CLAUDE_BIN` |
| exit 6 / `failed` with `devin rejected a tool call in --permission <perm>` | a tool was blocked by the permission mode; stdout holds partial text at most — never pass it off as the answer. Rerun with the rights the task actually needs, or accept the limit |
| exit 6 "empty answer" | devin ended the turn with no text and no rejection on stderr; check `logs` |
| `rejected a tool call that requires confirmation` on devin's stderr | the rejection above, seen from devin's side |
| job stuck in `running` with no output | check `logs`: no turns in the session yet means the run has not reached the model |
| answer looks invented | check `transcript` for whether files were read at all |

## Red lines (apply inside every devin run)

- Never commit, push or delete recursively on the strength of another harness's output.
- Never read, print or forward `.env`, `*.key`, `*.pem`, `credentials.json`. Naming an env
  var is fine, printing its value is not.
- Never install or authenticate on the human's behalf.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…