Skip to content

fix: preserve permission ordering by accepting a layered array#23214

Merged
rekram1-node merged 3 commits into
anomalyco:devfrom
asuffield:fix-permissions
May 12, 2026
Merged

fix: preserve permission ordering by accepting a layered array#23214
rekram1-node merged 3 commits into
anomalyco:devfrom
asuffield:fix-permissions

Conversation

@asuffield
Copy link
Copy Markdown
Contributor

@asuffield asuffield commented Apr 18, 2026

Issue for this PR

Closes #16157

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Merging permission objects with deep-merge silently discarded user-written rule ordering, so wildcards and specific patterns evaluated in unpredictable order across config sources.

Instead of merging objects, this preserves each config source's permission block as its own layer, converts each to a ruleset, and merges the rulesets. Later layers win, matching the existing intra-object ordering semantics.

New config syntax

permission now accepts either a single object (unchanged) or an array of layered configs:

{
  "permission": [
    // Layer 1: safety defaults
    { "bash": { "rm -rf *": "deny" } },
    // Layer 2: deny everything else that runs through bash
    { "bash": "deny" },
    // Layer 3: punch a hole through layer 2
    { "bash": { "echo *": "allow" } }
  ]
}

Each entry is one layer. Later layers override earlier rules (findLast semantics, same as inside a single object). Single-object configs work as before — they are treated as a one-element layer internally.

How did you verify your code works?

Added tests covering:

  • multi-file layered merging (global + project)
  • catchall overrides across layers
  • round-trip through Config.update / updateGlobal preserving the user's on-disk shape
  • existing key-order and parser tests still pass

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

…erve permissions objects as layers, convert them individually to rulesets, and merge the rulesets (anomalyco#16157)
Replace the internal permission_layers config field with a union on permission
itself (single object or array of layered configs). Add ConfigPermission.toLayers
to normalise at consumption sites. Schema has no decode transform, so user
files round-trip through Config.update / updateGlobal without their permission
section being rewritten into array form.
@rekram1-node
Copy link
Copy Markdown
Collaborator

rekram1-node commented May 12, 2026

slight change, instead of a separate config key merging them together w/ a union

@rekram1-node rekram1-node changed the title fix: do not merge permissions objects, merge rulesets fix: preserve permission ordering by accepting a layered array May 12, 2026
@rekram1-node rekram1-node merged commit 65368f6 into anomalyco:dev May 12, 2026
12 checks passed
@rekram1-node
Copy link
Copy Markdown
Collaborator

/review

@@ -708,11 +720,12 @@ export const layer = Layer.effect(
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: normalize the parsed env value with ConfigPermission.toLayers here too. If OPENCODE_PERMISSION uses the new array syntax, this appends the whole array as a single layer, so later consumers pass that array into Permission.fromConfig and interpret numeric array indexes as permission names.

// This preserves the ordering semantics: later rules override earlier rules.
// Each layer keeps the raw shape the user wrote on disk; consumers should use
// ConfigPermission.toLayers to normalise.
if (source.permission) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: consider using this layered merge in loadGlobal as well. Right now config.json, opencode.json, and opencode.jsonc are still combined with mergeConfig, so permission blocks split across global config files will still deep-merge into a single object before reaching this helper and can lose the user-written rule ordering this PR is trying to preserve.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

How are permissions applied in order?

2 participants