Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/onenot8/issueLoop/llms.txt

Use this file to discover all available pages before exploring further.

The permission system is what makes the fix-apply layer safe. Before IssueLoop executes any shell command as part of a proposed fix, it checks the command against an explicit allowlist defined in permission.yaml. If no matching entry exists, the command is blocked, logged, and a PermissionDenied exception is raised. Nothing runs implicitly — every allowed command must be declared.

permission.yaml structure

The file has two top-level sections: global entries apply to every repository, while per_repo entries apply only to the named repository. Both sections support exact string matches and regular expression patterns.
global:
  allowed_exact:    # exact string matches, any repo
    - "pytest tests/"
  allowed_patterns: # Python re.fullmatch patterns, any repo
    - 'pytest tests/.*'

per_repo:
  myrepo:
    allowed_exact: []
    allowed_patterns:
      - 'sed -i .* somefile\.py'
Global and per-repo entries are merged at check time — a command is allowed if it matches any entry from either section. The allowed_exact and allowed_patterns lists inside each section are independent; a command only needs to satisfy one of them.

Allowed exact vs allowed patterns

IssueLoop uses two distinct matching strategies: allowed_exact — the command string must match the listed value character-for-character. This is the most restrictive and most predictable option. Use it when the command is fixed and never varies.
global:
  allowed_exact:
    - "pytest tests/"
    - "black --check ."
allowed_patterns — the command string is tested against each pattern using Python’s re.fullmatch. The pattern must match the entire command string, not just a substring. Use this when the command includes variable arguments such as file paths or flags.
per_repo:
  myrepo:
    allowed_patterns:
      - 'sed -i .* somefile\.py'
      - 'python -m pytest tests/unit/test_.*\.py'
Patterns use re.fullmatch, not substring search. A broad pattern like 'sed .*' matches any sed command — including destructive ones like sed -i 's/.*//' important_file.py. Keep patterns as specific as possible, anchoring to exact file names or tightly bounded arguments.

Test manifest commands are always allowed

Commands listed in test_manifest.json for a given repository are automatically trusted for that repository. This covers the commands IssueLoop already knows how to run — pytest, unittest, or whatever runner the repo uses — so re-running tests after a fix never requires an explicit permission entry. The manifest is stored at data/test_manifest.json and is populated by issueloop.scan_repo(). Commands from the manifest count as an implicit allowed_exact set scoped to their repository. They are merged with any explicit entries before the check is evaluated.

Config file resolution order

IssueLoop resolves permission.yaml by searching the following locations in order, stopping at the first match:
  1. ISSUELOOP_PERMISSION_PATH environment variable — if set, the value is used as an absolute path to the config file, with no further searching.
  2. ./config/permission.yaml — relative to the current working directory when the process starts.
  3. config/permission.yaml in the IssueLoop checkout — the config/ directory at the root of the cloned repository (only relevant for development installs with -e .).
  4. Bundled package defaults — the _defaults/permission.yaml file shipped inside the issueloop package itself.
This means a standard pip install issueloop (non-editable) always falls back to the bundled defaults rather than failing silently. You only need to create a local config/permission.yaml when you actually want to allowlist specific commands.

Audit log

Every permission check — whether it results in an allow or a deny — is appended to data/logs/permission_audit.jsonl. Each line is a JSON object with the following fields:
FieldTypeDescription
tsISO-8601 stringUTC timestamp of the check.
eventstring"allowed_exact", "allowed_pattern", or "denied".
repostringThe repository name the command was checked against.
commandstringThe full command string that was evaluated.
detailstringFor "allowed_pattern" events, the pattern that matched. Empty for other events.
You can retrieve recent audit entries programmatically:
import issueloop

# All recent entries (default: last 50)
log = issueloop.get_permission_audit_log()

# Filtered to a specific repo
log = issueloop.get_permission_audit_log(repo="myrepo", limit=100)

PermissionDenied exception

run_guarded is the internal function that enforces permissions before running any shell command. If check_permission returns False, run_guarded raises PermissionDenied immediately — the subprocess is never spawned.
from issueloop.permissions import PermissionDenied

result = issueloop.apply_fix(ticket_id)
# apply_fix catches PermissionDenied internally and returns:
# {"status": "denied", ...}
When calling run_guarded directly (for custom tooling built on top of IssueLoop), you should handle PermissionDenied explicitly:
from issueloop.permissions import run_guarded, PermissionDenied

try:
    result = run_guarded("sed -i 's/old/new/' somefile.py", repo="myrepo")
except PermissionDenied as e:
    print(f"Blocked: {e}")
The error message includes both the blocked command and the name of the config file to edit, so the path to resolution is always clear.

Build docs developers (and LLMs) love