Skip to content

desktop: shell permission prompt has no description (missing settings.permissions.tool.shell i18n key) #59377

Open

Description

@pskraemer11

Summary

In the desktop app, a permission request for a shell command renders without the explanatory line. The dock resolves settings.permissions.tool.<action>.description from the i18n bundle, but the bundle only ships keys for bash, while the server emits the action name shell. Same dock logic as #51519 (where MCP permissions show only *).

Environment

  • opencode version: 2.1.4 (desktop app; the bundled opencode-cli.exe --version reports 2.1.4 as well)
  • OS: Windows 11 Home 25H2, build 26300, x64
  • Terminal: n/a — desktop app (Electron); no $TERM/$TERM_PROGRAM set
  • Shell: PowerShell 7 (pwsh), invoked through cmd.exe
  • Install/channel: release, channel=latest, desktop build (not a dev checkout)
  • Active plugins: permission-guard (local plugin, registers ctx.permission.hook("evaluate")), custom-tools

Reproduction

  1. Add a permission rule that asks for shell commands, e.g. { "action": "shell", "resource": "*", "effect": "ask" } in ~/.config/opencode/opencode.jsonc.
  2. Run a shell command that no later rule allows.
  3. Observe the permission card in the desktop app.

Expected Behavior

The card shows a description line identifying what is being asked, plus the affected command — at minimum the same text the TUI shows.

Actual Behavior

The card shows only the title ("Permission required"), the warning icon and the three buttons. The description line is missing. When the request also carries no resources, the body is completely empty and the user cannot tell what they are approving.

Additional Context

Evidence that the description is unreachable for shell:

  • The renderer builds the body from exactly two sources: the i18n key settings.permissions.tool.${request.action}.description (rendered only when the key resolves; otherwise an empty string is used) and request.resources. The message field is not rendered at all.
  • The 2.1.4 bundle contains settings.permissions.tool.bash.title and settings.permissions.tool.bash.description, but no settings.permissions.tool.shell.* entry at all.
  • The server uses shell as the action name — e.g. the default agent permission rules in the shipped binary read { "action": "shell", "resource": "*", "effect": "allow" }, and the same config vocabulary (shell, read, edit, external_directory) is accepted in user config.

So for every shell permission the description lookup fails by construction, independent of locale.

Suggested fix: add settings.permissions.tool.shell.title / .description to the i18n bundle, or map the legacy bash entries onto shell in the lookup.

Two side notes from the same investigation:

  • The dock only special-cases a fixed set of action names. The bundle has entries for bash, doom_loop, edit, external_directory, glob, grep, list, lsp, read, skill, task, todowrite, webfetch, websearch — anything else (MCP tool names, per-tool actions) silently produces no hint, which is the broader form of v2 web/desktop: MCP permission prompt shows only *, not the tool name #51519.
  • Unrelated packaging oddity: the version resource of resources/opencode-cli.exe in the desktop build says 1.4.4, while opencode-cli.exe --version and the log (version=2.1.4 channel=latest) both report 2.1.4. This made version triage misleading.

Activity

  1. opencode-agent commented on Oct 26, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I reproduced this on 2.1.4, and it still happens on the latest dev (ae876ee).

    With a { "action": "shell", "resource": "*", "effect": "ask" } rule and a model calling the shell tool on echo hello, the server's pending permission request has action: "shell" and resources: ["echo hello"]. The web/desktop permission dock looks up settings.permissions.tool.shell.description, but the app's translation files only define settings.permissions.tool.bash.* ("Run shell commands"). So the description line is never shown for shell permissions, in any locale. The script below reproduces the server side and prints the key the dock looks up.

    I couldn't render the desktop app itself here, and I didn't test the fully empty card for a request with no resources, since my test request always included the command. The TUI shows the shell prompt normally.

    Related: #51519 is the broader version of this, where MCP and other per-tool permissions get no hint at all.

    Reproduction script (drive.ts)
    // Repro for #59377: a shell permission request carries action "shell", but the
    // web/desktop permission dock (packages/app/src/session/requests/session-permission-dock.tsx)
    // looks up `settings.permissions.tool.<action>.description`, and the app's i18n bundle
    // only defines `settings.permissions.tool.bash.*` -> the description line is never shown.
    import { Effect, Stream } from "effect"
    import { Llm, OpenCodeDriver } from "opencode-drive"
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        tui: { recording: true },
        config: { permissions: [{ action: "shell", resource: "*", effect: "ask" }] } as any,
      },
      ({ ui, llm, opencode }) =>
        Effect.gen(function* () {
          let calls = 0
          yield* llm.serve((req: any) => {
            const system = String(req?.body?.messages?.[0]?.content ?? "")
            if (system.includes("You name conversations")) return Stream.make(Llm.text("Echo hello"))
            calls++
            return calls === 1
              ? Stream.make(
                  Llm.toolCall({ index: 0, id: "call_shell", name: "shell", input: { command: "echo hello" } }),
                  Llm.finish("tool-calls"),
                )
              : Stream.make(Llm.text("done"))
          })
          yield* ui.submit("Run echo hello")
          yield* Effect.sleep("5 seconds")
          yield* ui.screenshot("tui-permission")
          const pending: any = yield* opencode.permission.request.list()
          const actions = (pending.data ?? []).map((r: any) => r.action)
          console.log("PENDING PERMISSION REQUESTS:", JSON.stringify(pending.data, null, 2))
          console.log("ACTIONS:", actions.join(", "))
          console.log(
            "Desktop dock looks up:",
            actions.map((a: string) => `settings.permissions.tool.${a}.description`).join(", "),
          )
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

  2. pskraemer11 commented on Oct 26, 2026

    @pskraemer11
    Author

    Refinement after reading the source — resources is not empty for shell.

    packages/core/src/tool/plugin/shell.ts on dev:

    export const name = "shell"
    ...
    const parsed = yield* ShellParse.scan(invocation.command, invocation.shell, target.absolute, { portable })
    ...
    if (parsed.commands.length > 0)
      yield* permission.assert({
        action: name,
        resources: parsed.commands.map((command) => command.resource),
        save: parsed.commands.map((command) => command.save),
        ...

    So a shell permission always carries the parsed command(s) in resources, and the dock renders them as <code> lines. The body is therefore not empty — what is missing is only the description line above the command.

    This narrows the reported impact: the user can still see what is being run, they just get no hint line naming the action. A completely empty body, or one showing a bare *, must come from a different action — e.g. MCP tools, whose wrapper passes resources: ["*"]. That is #51519, not this issue.

    The fix is unchanged: add settings.permissions.tool.shell.title / .description to the i18n bundle, or map the existing bash entries onto shell in the lookup. The root cause — the bundle has bash while the server emits shell — is confirmed by the same file (export const name = "shell").

  3. ZXLyyds100 commented on Oct 26, 2026

    @ZXLyyds100

    Implemented in #59405.

    • Added settings.permissions.tool.shell.title and .description, matching the existing shell wording.
    • Extended the existing permission-dock regression to assert the visible “Run shell commands” line.
    • Focused Chromium regression: 1 passed.
    • App unit command: 898 passed, 0 failed; app typecheck: 1,734 files, 0 errors.
    • Repository check: 35/35 typecheck tasks passed, 0 errors; existing lint warnings remain.

    The change is limited to the missing shell description; the parsed command resources remain visible, and MCP/other permission actions are unchanged. Real before/after captures from the same Chromium fixture are available as before and after.

    Implemented and verified with OpenAI Codex assistance. Hosted checks currently pass for standards, compliance, and contributor labeling; human review and any remaining CI are not implied.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions