Skip to content

Profiles: relative paths in extends, and save-on-exit to an extended profile #2065

Description

@ranguard

What problem are you trying to solve?

The maintainers agreed the general idea. This issue records the plan and scope.

  1. extends accepts only a profile name or a registry ref (org/pack). It does not accept a file path. To share a base profile between project profiles, you must put the base in the same directory or in ~/.config/nono/profiles/.
  2. When nono exits with denials, it offers to save grants. It saves only to the top-level profile, or to a new user profile. It cannot save to a profile that the top-level profile extends.
  3. Bug: if you run --profile ./path/to/profile.json, the save prompt cannot update that file. It creates a new user profile, and the new profile does not extend your file. The grants are then not used with your profile.
  4. Bug: the save writes the file again with serde_json. This removes all JSONC comments and adds all default fields.

What would you like to see?

Phase 1: relative paths in extends

  • An extends entry that starts with ./ or ../ is a path.
  • nono resolves the path from the canonical directory of the file that contains the entry.
  • org/pack stays a registry ref. A bare name keeps the current lookup order.
  • Cycle detection uses the canonical path for path entries.
  • A profile in the pack store must not use a path entry. nono rejects it with an error.
  • A profile in profile-drafts/ must not use a relative path entry, because profile promote moves the file. nono rejects it with an error.
  • These callers must accept path entries: load_profile_extends, package_status::walk_extends_chain, profile show/diff/validate, profile init --extends, why, and --extends on the CLI (relative to the current directory).
  • Fix bug 3: when --profile is a file path, the save prompt offers to update that file.
  • Fix bug 4: save edits use the cst feature of jsonc-parser (already a dependency). The edit adds only new entries. Comments and layout stay.

Phase 2: choose the save target on exit

Flow:

  1. The session ends with denials. nono builds the patch (no change).
  2. The user selects the items to grant or suppress (no change).
  3. nono gets the list of files in the resolved extends chain, with the source of each file.
  4. nono removes these sources from the list:
    • Pack store profiles. The pack verification fails if the file changes, and nono pull replaces the file.
    • Built-in profiles. They are not files.
  5. If the list is empty, nono offers to create a new user profile (current behaviour).
  6. If the list has one file, nono asks to update that file (current behaviour).
  7. If the list has more than one file, nono shows the list. The top-level profile is first and is the default. The bases follow in chain order. The last option is "new user profile".
  8. For a base profile, the prompt shows that the grant applies to all profiles that extend that base.
  9. nono writes all selected items to one file. (Selecting a target for each item is out of scope.)

Out of scope

  • Absolute paths and ~/ paths in extends. Reasons:
    • The use case is a shared base inside one project tree. Relative paths cover it.
    • A base in the user profile directory is already available by bare name. ~/ adds a second way to do the same thing.
    • Absolute and ~/ paths make a profile specific to one machine and one user. The profile fails when you share it in a repository.
    • ~/ needs a home directory source. HOME is an environment variable, and nono treats environment variables as untrusted. This needs its own design.
    • We can add them later without a breaking change, because nono rejects them now.
  • A different save target for each item.
  • Changes to the sandbox library or enforcement.

What have you tried instead?

  • Put the base profile in the same directory as the child, and extend it by name (sibling lookup).
  • Put the base profile in ~/.config/nono/profiles/, and extend it by name. This does not work for a base that is part of a project repository.
  • After the save prompt, move grants to the base profile manually.

How is this blocking you?

It would be a nice improvement but I have a workaround

Additional context

Risks

  • A path entry can point to any file that the user can read. This adds no new trust: the top-level profile can already grant the same access directly. The pack store restriction stops a pack from loading files outside the pack.
  • A grant saved to a shared base applies to more profiles. The prompt in step 8 tells the user.
  • A project profile can be in a git repository. A grant saved there can be committed. The prompt shows the full file path.

Tests

  • Path resolution: ./, ../, symlinked directories, cycles through paths, missing file.
  • Rejection: path entry in a pack store profile, relative entry in a draft.
  • Save: update a path profile; keep JSONC comments; target list excludes packs and built-ins; default is the top-level profile.

This issue was drafted with help from Claude Code (AI agent).

Activity

  1. nogent-nolabs-ai commented on Oct 6, 2026

    @nogent-nolabs-ai

    🛡️ nogent issue triage

    Assessment: needs-code-change

    Suggested resolution: Implement the proposed Phase 1 and Phase 2 changes in the Rust codebase of crates/nono-cli, specifically targeting profile loading, extends-chain resolution, and the save-on-exit interactive prompt handling.

    Notes for maintainers: This involves modifying crates/nono-cli (specifically profile loading/resolution logic and the interactive saving prompt flow) and utilizing the jsonc-parser crate to preserve comments as outlined in the issue description.

    Automated triage suggestion. Not a maintainer decision.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions