---
module: devices.agent-scripts
title: Agent Scripts
tab: Internal
order: 20
audience: internal
---

> **Internal · ops/support.** Grounded in `hiveops-devices` source (2026-07-01). This is a CRUD store today; the "Push to fleet" delivery path is **not wired up** in the backend — read the failure notes below before telling a customer a script was delivered.

## What this module actually is

A per-fleet library of shell scripts (`.cmd` / `.ps1` / `.sh`) stored in the **hiveops-devices** backend. Screen lives under **Settings → Agent Scripts** in the Devices SPA (`AgentScripts.svelte`). Owned entirely by hiveops-devices — no other service, no Kafka, no agent contact from this module's own endpoints.

- **Service:** `hiveops-devices` (Spring Boot, port 8096, DB `hiveiq_devices`)
- **Table:** `agent_scripts`
- **API base:** `/api/fleet/scripts` (yes — `fleet` in the path, but it is served by devices, not hiveops-fleet)

## Roles required

Every endpoint on `AgentScriptController` is gated at the class level:

```java
@PreAuthorize("hasAnyRole('MSP_ADMIN','BCOS_ADMIN')")
```

So **all** actions (list, create, edit, delete, push) require `MSP_ADMIN` or `BCOS_ADMIN`. There is no read-only role. A user without one of these roles gets 403 on the initial `GET /api/fleet/scripts` and the list simply fails to load.

## Admin actions

| Action | Endpoint | Notes |
|--------|----------|-------|
| List scripts | `GET /api/fleet/scripts` | Optional `?institutionKey=`; the SPA never sends it, so the list shows **all** institutions' scripts. |
| Add | `POST /api/fleet/scripts` | Name + content required (blank → 400). Name must end `.cmd`/`.sh`/`.ps1` (validated **client-side only**). |
| Edit | `PUT /api/fleet/scripts/{id}` | SPA sends `{description, content}` only — name is locked after create. |
| Delete | `DELETE /api/fleet/scripts/{id}` | Hard delete, 204. No soft-delete, no audit row. |
| Push | `POST /api/fleet/scripts/{id}/push` | **No-op stub** — see below. |

## First things to check when it misbehaves

1. **"I pushed a script but nothing happened on the ATMs."** Expected. `POST /api/fleet/scripts/{id}/push` is a **stub** — it returns `200 OK` with an empty list and creates **no** fleet task. The SPA still shows a green "pushed to N ATMs" toast. Do not tell the customer a script was delivered based on that toast. (The real signed-delivery pipeline lives in **hiveops-fleet** as `UPDATE_SCRIPT` tasks, but this module's Push button is not connected to it.)
2. **List won't load / 403.** User lacks `MSP_ADMIN`/`BCOS_ADMIN`, or the JWT didn't reach the devices backend. Check the token role claim and that the request hit `api.bcos.cloud/devices` (devices backend), not the incident/fleet base.
3. **List empty but scripts exist.** Confirm you're pointed at the right DB — `agent_scripts` in `hiveiq_devices` (devices VM path via ProxyJump to `10.10.10.188`). Scripts created here have `institution_key = NULL` (SPA never sets it), so any institution-scoped query returns nothing.
4. **Can't rename a script.** By design — the name field is disabled on edit and the SPA doesn't send `name` on `PUT`. To rename, delete + recreate.
5. **Save rejected "Name must end with .cmd, .sh, or .ps1".** That check is **client-side only**. A direct `POST` with any name will be accepted by the backend as long as name+content are non-blank.

## Common failure modes + fixes

| Symptom | Cause | Fix |
|---------|-------|-----|
| Push toast succeeds, no ATM change | Backend push is a stub (returns `[]`) | Deliver via hiveops-fleet `UPDATE_SCRIPT` fleet task instead; treat this Push button as non-functional. |
| 403 on load | Missing admin role | Grant `MSP_ADMIN`/`BCOS_ADMIN`. |
| Scripts from other institutions visible | SPA lists unscoped (no `institutionKey`) | Known; filter is supported in the API but unused in the UI. |
| Edit blanks a description | `PUT` overwrites `description` with the request value each time | Always resend the full description on edit (SPA does this). |

## Quick DB check

```sql
-- on hiveiq_devices (database VM 10.10.10.188, via ProxyJump)
SELECT id, name, institution_key, updated_at FROM agent_scripts ORDER BY name;
```

See also the customer-facing **Overview** tab, the **Architect** tab for the intended signed-delivery design, and [platform.service-ownership].
