fix(permissions): silence non-destructive ask prompts for autonomous ops #54

Closed
opened 2026-08-11 22:33:04 +03:00 by slaid098 · 0 comments
Owner

Контекст

Пользователь — соло-разработчик. Даёт агенту задачу и уходит спать. Ночные автономные запуски зависают на ask промптах в opencode.json (блок permission, строки 161-408 файла .opencode/opencode.json). Всего 55 записей ask в блоке bash + отсутствие catch-all в блоке read → дефолтное поведение opencode (запрос разрешения на чтение любого файла не из явных 9 шаблонов).

Конкретные жалобы пользователя:

  1. Промпты на чтение файлов с "example" в пути/имени (например src/examples/foo.rs, assets/example.png) — блок read не имеет catch-all "*": "allow", поэтому неописанные пути попадают в дефолт ask. .env.example уже allow (строка 170), но другие "example" файлы — нет.
  2. Промпты на удаление git-тегов (git tag -d, git push --delete) — нужно для release skill.
  3. Промпты на force push, docker stop/rm/restart, kill/nohup — блокируют автономную работу.

Задача

Уменьшить количество ask промптов в .opencode/opencode.json так, чтобы ночные автономные запуски не зависали. Изменить 30 записей ask → allow, 10 force push записей ask → allow, добавить catch-all "*": "allow" в начало блока read. Оставить ask только для 22 SSH опасных команд (rm/reboot/shutdown/apt/systemctl stop на удалённых хостах) — оркестратор/general их сам не вызывает, ночью не нужны.

Контракты

Файл: /root/workspace/opencode-config/.opencode/opencode.json, блок permission (строки 161-408).

Изменение 1 — блок read (строки 165-175): добавить catch-all ПЕРВЫМ

Текущий блок read (9 записей, без catch-all):

"read": {
  "*.env": "deny",
  ".env": "deny",
  "**/.env": "deny",
  "**/.env*": "deny",
  "**/.env.example": "allow",
  "**/.env.template": "allow",
  "**/id_ed25519": "deny",
  "**/id_rsa": "deny",
  "**/id_ed25519.pub": "allow"
}

Должно стать (добавить "*": "allow" ПЕРВЫМ ключом — КРИТИЧНО из-за findLast, иначе * переопределит deny для .env/ключей):

"read": {
  "*": "allow",
  "*.env": "deny",
  ".env": "deny",
  "**/.env": "deny",
  "**/.env*": "deny",
  "**/.env.example": "allow",
  "**/.env.template": "allow",
  "**/id_ed25519": "deny",
  "**/id_rsa": "deny",
  "**/id_ed25519.pub": "allow"
}

Это убирает промпты на чтение всех несекретных файлов (включая "example" файлы кроме .env.example, который уже allow).

Изменение 2 — git tag/branch delete: 8 записей ask → allow

Строки и точные ключи:

  • "git -C * branch -d *" (строка 222)
  • "git -C * push --delete *" (строка 223)
  • "git -C * push origin --delete *" (строка 224)
  • "git branch -d *" (строка 296)
  • "git push --delete *" (строка 297)
  • "git push origin --delete *" (строка 298)
  • "git tag -d *" (строка 392)
  • "git -C * tag -d *" (строка 398)

Все → "allow".

Изменение 3 — force push: 10 записей ask → allow

Строки и точные ключи:

  • "git push --force*" (387)
  • "git push -f*" (388)
  • "git push * --force*" (389)
  • "git push * -f*" (390)
  • "git push * :*" (391)
  • "git -C * push --force*" (393)
  • "git -C * push -f*" (394)
  • "git -C * push * --force*" (395)
  • "git -C * push * -f*" (396)
  • "git -C * push * :*" (397)

Все → "allow". Обоснование: соло-разраб, чужой работы нет, branch protection на Forgejo ловит main на серверной стороне.

Изменение 4 — docker: 7 записей ask → allow

  • "docker restart *" (299)
  • "docker stop *" (300)
  • "docker rm *" (301)
  • "docker exec * sh *" (305)
  • "docker exec * bash *" (306)
  • "docker system prune*" (307)
  • "docker volume rm *" (308)

Все → "allow". Оставить deny для docker exec * rm * (303) и docker exec * rmdir * (304) — они уже deny.

Изменение 5 — прочее: 5 записей ask → allow

  • "del *" (315)
  • "Remove-Item *" (316)
  • "vastai *" (319) — но vastai show* (320) и vastai search* (321) уже allow, остаются как есть
  • "kill *" (322)
  • "nohup *" (323)

Все → "allow".

ИТОГО изменяемых записей: 1 (read catch-all) + 8 + 10 + 7 + 5 = 31 запись.

Инварианты

  • Все deny записи остаются БЕЗ ИЗМЕНЕНИЙ: .env*, id_ed25519, id_rsa, *.pem, *secrets*, git reset, git clean, git clone, clone.sh, *-status.py deny-правила, curl -X DELETE *FORGEJO_URL*, docker exec * rm *, docker exec * rmdir *, ssh * mkfs*, ssh * dd *, ssh * fdisk*, cat .env*, *cat */.env*, cat *id_ed25519*, *cat *ssh/id_ed25519*, doom_loop.
  • 22 SSH опасные команды остаются ask: ssh * cat *.env*, ssh * cat .env*, ssh * rm *, ssh * rmdir *, ssh * docker restart*, ssh * docker stop*, ssh * docker rm*, ssh * docker rmi*, ssh * docker update*, ssh * systemctl restart*, ssh * systemctl stop*, ssh * apt *, ssh * yum *, ssh * mv *, ssh * chmod *, ssh * chown *, ssh * reboot*, ssh * shutdown*, ssh * powershell -Command Set-*, ssh * powershell -Command New-*, ssh * powershell -Command Remove-*, ssh * powershell -Command Restart-*, ssh * powershell -Command Stop-*.
  • Порядок ключей в read блоке: catch-all "*": "allow" ДОЛЖЕН быть первым (findLast — последнее совпадение побеждает, конкретные deny должны идти после * чтобы побеждать для секретов).
  • Все остальные блоки permission (edit, external_directory, doom_loop) не трогать.
  • Метаданные агентов (.opencode/agents/reviewer.md, .opencode/agents/memory-syncer.md) не трогать — у них свой permission блок с "*": deny.
  • Файл должен остаться валидным JSON (запятые, кавычки).

Граничные случаи

  1. findLast ordering в read: если "*": "allow" поставить в конец блока read, то для .env совпадут **/.env* (deny) и * (allow), findLast выберет * (последнее) → .env станет allow = ДЫРА. Поэтому * обязан быть ПЕРВЫМ. Конкретные deny для .env/ключей идут после и побеждают для своих паттернов.
  2. **/.env.example уже allow (строка 170) — catch-all * не меняет поведение (оба allow), но порядок: * первый, **/.env.example allow после — оба allow, конфликта нет.
  3. git push * :* (391) — устаревший синтаксис удаления ref. Перевод в allow разрешает git push origin refs/heads/foo:refs/heads/bar (законное переименование ref). Это безопасно для соло.
  4. vastai * (319) → allow: vastai show* (320) и vastai search* (321) уже allow, остаются. findLast: конкретные правила позже побеждают, но они оба allow — конфликта нет.
  5. JSON валидность: после последней записи в блоке bash (sh */clone.sh*": "deny") не должно быть trailing comma. После добавления "*": "allow" в начало read — запятая после него обязательна.

Влияние на связанные компоненты

  • release skill (.opencode/skills/release/SKILL.md) — использует git tag -d и git push --delete для пересоздания тегов. После изменения они allow → release pipeline не зависнет.
  • run-pipeline skill — merge использует squash, force push не нужен в нормальном flow, но при ребейзе/аменде может потребоваться. Теперь allow.
  • reviewer/memory-syncer агенты — имеют собственные permission блоки с "*": deny, на них изменения не влияют.
  • check-permissions.py линтер (упомянут в configure-opencode/SKILL.md:124) — может срабатывать на опасные правила. Проверить, не начнёт ли он ругаться на force push allow / docker rm allow. Если да — возможно нужно обновить линтер, но это вне scope (см. ниже).

Вне scope

  • Не обновлять check-permissions.py (если он ругается — отдельный issue).
  • Не трогать метаданные агентов (reviewer.md, memory-syncer.md).
  • Не менять edit / external_directory / doom_loop.
  • Не добавлять catch-all в bash (например "*": "allow") — это было бы опасно.
  • Не трогать webfetch (его нет в конфиге).
  • Не синхронизировать ~/.config/opencode/opencode.json — это bind-mount к workspace clone, изменения применяются автоматически.

Критерии приемки

  1. В блоке read добавлена запись "*": "allow" ПЕРВОЙ по порядку (до *.env deny).
  2. 8 записей git tag/branch delete переведены ask → allow.
  3. 10 записей force push переведены ask → allow.
  4. 7 записей docker stop/rm/restart/prune переведены ask → allow.
  5. 5 записей del/Remove-Item/vastai/kill/nohup переведены ask → allow.
  6. 22 SSH опасные записи остались ask (не изменены).
  7. Все deny записи (секреты, git reset/clean/clone, status.py, ssh mkfs/dd/fdisk, cat .env*/ключи, docker exec rm/rmdir, curl DELETE FORGEJO, clone.sh) остались deny.
  8. Файл .opencode/opencode.json остался валидным JSON (проверить через python -c "import json; json.load(open('.opencode/opencode.json'))").
  9. После правки: grep -c '"ask"' .opencode/opencode.json должен вернуть 22 (только SSH записи).
  10. Пользователь больше не получает промпт на чтение файлов с "example" в пути/имени (кроме .env который deny).
## Контекст Пользователь — соло-разработчик. Даёт агенту задачу и уходит спать. Ночные автономные запуски зависают на `ask` промптах в `opencode.json` (блок `permission`, строки 161-408 файла `.opencode/opencode.json`). Всего 55 записей `ask` в блоке `bash` + отсутствие catch-all в блоке `read` → дефолтное поведение opencode (запрос разрешения на чтение любого файла не из явных 9 шаблонов). Конкретные жалобы пользователя: 1. Промпты на чтение файлов с "example" в пути/имени (например `src/examples/foo.rs`, `assets/example.png`) — блок `read` не имеет catch-all `"*": "allow"`, поэтому неописанные пути попадают в дефолт `ask`. `.env.example` уже `allow` (строка 170), но другие "example" файлы — нет. 2. Промпты на удаление git-тегов (`git tag -d`, `git push --delete`) — нужно для release skill. 3. Промпты на force push, docker stop/rm/restart, kill/nohup — блокируют автономную работу. ## Задача Уменьшить количество `ask` промптов в `.opencode/opencode.json` так, чтобы ночные автономные запуски не зависали. Изменить 30 записей `ask` → `allow`, 10 force push записей `ask` → `allow`, добавить catch-all `"*": "allow"` в начало блока `read`. Оставить `ask` только для 22 SSH опасных команд (rm/reboot/shutdown/apt/systemctl stop на удалённых хостах) — оркестратор/general их сам не вызывает, ночью не нужны. ## Контракты Файл: `/root/workspace/opencode-config/.opencode/opencode.json`, блок `permission` (строки 161-408). ### Изменение 1 — блок `read` (строки 165-175): добавить catch-all ПЕРВЫМ Текущий блок `read` (9 записей, без catch-all): ```json "read": { "*.env": "deny", ".env": "deny", "**/.env": "deny", "**/.env*": "deny", "**/.env.example": "allow", "**/.env.template": "allow", "**/id_ed25519": "deny", "**/id_rsa": "deny", "**/id_ed25519.pub": "allow" } ``` Должно стать (добавить `"*": "allow"` ПЕРВЫМ ключом — КРИТИЧНО из-за findLast, иначе `*` переопределит deny для .env/ключей): ```json "read": { "*": "allow", "*.env": "deny", ".env": "deny", "**/.env": "deny", "**/.env*": "deny", "**/.env.example": "allow", "**/.env.template": "allow", "**/id_ed25519": "deny", "**/id_rsa": "deny", "**/id_ed25519.pub": "allow" } ``` Это убирает промпты на чтение всех несекретных файлов (включая "example" файлы кроме `.env.example`, который уже allow). ### Изменение 2 — git tag/branch delete: 8 записей `ask` → `allow` Строки и точные ключи: - `"git -C * branch -d *"` (строка 222) - `"git -C * push --delete *"` (строка 223) - `"git -C * push origin --delete *"` (строка 224) - `"git branch -d *"` (строка 296) - `"git push --delete *"` (строка 297) - `"git push origin --delete *"` (строка 298) - `"git tag -d *"` (строка 392) - `"git -C * tag -d *"` (строка 398) Все → `"allow"`. ### Изменение 3 — force push: 10 записей `ask` → `allow` Строки и точные ключи: - `"git push --force*"` (387) - `"git push -f*"` (388) - `"git push * --force*"` (389) - `"git push * -f*"` (390) - `"git push * :*"` (391) - `"git -C * push --force*"` (393) - `"git -C * push -f*"` (394) - `"git -C * push * --force*"` (395) - `"git -C * push * -f*"` (396) - `"git -C * push * :*"` (397) Все → `"allow"`. Обоснование: соло-разраб, чужой работы нет, branch protection на Forgejo ловит main на серверной стороне. ### Изменение 4 — docker: 7 записей `ask` → `allow` - `"docker restart *"` (299) - `"docker stop *"` (300) - `"docker rm *"` (301) - `"docker exec * sh *"` (305) - `"docker exec * bash *"` (306) - `"docker system prune*"` (307) - `"docker volume rm *"` (308) Все → `"allow"`. Оставить `deny` для `docker exec * rm *` (303) и `docker exec * rmdir *` (304) — они уже deny. ### Изменение 5 — прочее: 5 записей `ask` → `allow` - `"del *"` (315) - `"Remove-Item *"` (316) - `"vastai *"` (319) — но `vastai show*` (320) и `vastai search*` (321) уже allow, остаются как есть - `"kill *"` (322) - `"nohup *"` (323) Все → `"allow"`. ### ИТОГО изменяемых записей: 1 (read catch-all) + 8 + 10 + 7 + 5 = 31 запись. ## Инварианты - Все `deny` записи остаются БЕЗ ИЗМЕНЕНИЙ: `.env*`, `id_ed25519`, `id_rsa`, `*.pem`, `*secrets*`, `git reset`, `git clean`, `git clone`, `clone.sh`, `*-status.py` deny-правила, `curl -X DELETE *FORGEJO_URL*`, `docker exec * rm *`, `docker exec * rmdir *`, `ssh * mkfs*`, `ssh * dd *`, `ssh * fdisk*`, `cat .env*`, `*cat */.env*`, `cat *id_ed25519*`, `*cat *ssh/id_ed25519*`, `doom_loop`. - 22 SSH опасные команды остаются `ask`: `ssh * cat *.env*`, `ssh * cat .env*`, `ssh * rm *`, `ssh * rmdir *`, `ssh * docker restart*`, `ssh * docker stop*`, `ssh * docker rm*`, `ssh * docker rmi*`, `ssh * docker update*`, `ssh * systemctl restart*`, `ssh * systemctl stop*`, `ssh * apt *`, `ssh * yum *`, `ssh * mv *`, `ssh * chmod *`, `ssh * chown *`, `ssh * reboot*`, `ssh * shutdown*`, `ssh * powershell -Command Set-*`, `ssh * powershell -Command New-*`, `ssh * powershell -Command Remove-*`, `ssh * powershell -Command Restart-*`, `ssh * powershell -Command Stop-*`. - Порядок ключей в `read` блоке: catch-all `"*": "allow"` ДОЛЖЕН быть первым (findLast — последнее совпадение побеждает, конкретные deny должны идти после `*` чтобы побеждать для секретов). - Все остальные блоки `permission` (`edit`, `external_directory`, `doom_loop`) не трогать. - Метаданные агентов (`.opencode/agents/reviewer.md`, `.opencode/agents/memory-syncer.md`) не трогать — у них свой permission блок с `"*": deny`. - Файл должен остаться валидным JSON (запятые, кавычки). ## Граничные случаи 1. **findLast ordering в `read`**: если `"*": "allow"` поставить в конец блока `read`, то для `.env` совпадут `**/.env*` (deny) и `*` (allow), findLast выберет `*` (последнее) → `.env` станет allow = ДЫРА. Поэтому `*` обязан быть ПЕРВЫМ. Конкретные deny для `.env`/ключей идут после и побеждают для своих паттернов. 2. **`**/.env.example` уже allow (строка 170)** — catch-all `*` не меняет поведение (оба allow), но порядок: `*` первый, `**/.env.example` allow после — оба allow, конфликта нет. 3. **`git push * :*` (391)** — устаревший синтаксис удаления ref. Перевод в allow разрешает `git push origin refs/heads/foo:refs/heads/bar` (законное переименование ref). Это безопасно для соло. 4. **`vastai *` (319) → allow**: `vastai show*` (320) и `vastai search*` (321) уже allow, остаются. findLast: конкретные правила позже побеждают, но они оба allow — конфликта нет. 5. **JSON валидность**: после последней записи в блоке `bash` (`sh */clone.sh*": "deny"`) не должно быть trailing comma. После добавления `"*": "allow"` в начало `read` — запятая после него обязательна. ## Влияние на связанные компоненты - **release skill** (`.opencode/skills/release/SKILL.md`) — использует `git tag -d` и `git push --delete` для пересоздания тегов. После изменения они `allow` → release pipeline не зависнет. - **run-pipeline skill** — merge использует squash, force push не нужен в нормальном flow, но при ребейзе/аменде может потребоваться. Теперь `allow`. - **reviewer/memory-syncer агенты** — имеют собственные permission блоки с `"*": deny`, на них изменения не влияют. - **check-permissions.py** линтер (упомянут в `configure-opencode/SKILL.md:124`) — может срабатывать на опасные правила. Проверить, не начнёт ли он ругаться на force push allow / docker rm allow. Если да — возможно нужно обновить линтер, но это вне scope (см. ниже). ## Вне scope - Не обновлять `check-permissions.py` (если он ругается — отдельный issue). - Не трогать метаданные агентов (reviewer.md, memory-syncer.md). - Не менять `edit` / `external_directory` / `doom_loop`. - Не добавлять catch-all в `bash` (например `"*": "allow"`) — это было бы опасно. - Не трогать `webfetch` (его нет в конфиге). - Не синхронизировать `~/.config/opencode/opencode.json` — это bind-mount к workspace clone, изменения применяются автоматически. ## Критерии приемки 1. В блоке `read` добавлена запись `"*": "allow"` ПЕРВОЙ по порядку (до `*.env` deny). 2. 8 записей git tag/branch delete переведены `ask` → `allow`. 3. 10 записей force push переведены `ask` → `allow`. 4. 7 записей docker stop/rm/restart/prune переведены `ask` → `allow`. 5. 5 записей del/Remove-Item/vastai/kill/nohup переведены `ask` → `allow`. 6. 22 SSH опасные записи остались `ask` (не изменены). 7. Все `deny` записи (секреты, git reset/clean/clone, status.py, ssh mkfs/dd/fdisk, cat .env*/ключи, docker exec rm/rmdir, curl DELETE FORGEJO, clone.sh) остались `deny`. 8. Файл `.opencode/opencode.json` остался валидным JSON (проверить через `python -c "import json; json.load(open('.opencode/opencode.json'))"`). 9. После правки: `grep -c '"ask"' .opencode/opencode.json` должен вернуть 22 (только SSH записи). 10. Пользователь больше не получает промпт на чтение файлов с "example" в пути/имени (кроме `.env` который deny).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
slaid098/opencode-config#54
No description provided.