Skip to content

Folder dialog silently drops hyphens, so the console cannot create folders it already displays #988

Description

@DaviDevMod

Describe the bug

The Create a Folder dialog filters keystrokes through an alphanumeric-only regex, so - and _ cannot be typed. The character is dropped silently — no error, no hint, the key simply does nothing.

Two things make this a bug rather than a naming policy:

  1. Nothing else in the stack enforces it. normalize_path_string does no character validation at all (it only collapses //, adds a leading / and strips a trailing one), and create_environment_folder_structure calls get_or_create(name=segment, …) with whatever segment string it is handed. POST /v1/secrets normalises the path and calls that helper for any non-root path, so creating a secret at --path /workers/user-service creates the folder that the dialog refuses to name.
  2. The console already displays folders it cannot create. Kebab-case folders created via the CLI/API render, expand, sync and resolve references normally. So the UI is showing valid state that its own primary control cannot produce — and it took reading the frontend source to discover why the keyboard appeared broken.

The failure mode is what makes it costly: because the rejection is silent, the reasonable conclusion is that something is wrong with your keyboard, your browser, or your permissions. There is no way to tell from the UI that a rule was applied.

Relevant code, at main (6a46708):

// frontend/app/[team]/apps/[app]/environments/[environment]/[[...path]]/page.tsx
const NewFolderMenu = () => {
  const [name, setName] = useState<string>('')

  // Regular expression to match only alphanumeric characters
  const regex = /^[a-zA-Z0-9]*$/        // L1007 — shadowed by the one below, never read

  const handleUpdateName = (newName: string) => {
    // Regular expression to match only alphanumeric characters
    const regex = /^[a-zA-Z0-9]*$/       // L1024 — this is the one that runs
    if (regex.test(newName) || newName === '') {
      setName(newName)
    }
  }

(Minor aside while you are in there: the copy at L1007 is dead — shadowed by the identical one declared inside handleUpdateName.)

To Reproduce

  1. Open any app → environment → Create a Folder.
  2. Type user-service in Folder name.
  3. The field shows userservice; the hyphen never appears and no message is shown.
  4. From a shell, phase secrets create SOME_KEY --app <app> --env <env> --path /user-service succeeds and the folder user-service appears in the console.

Expected behavior

Either of these would resolve it — the first is preferable:

  • Accept what the backend accepts. PR #364 already proposed exactly this (/^[a-zA-Z0-9_-]*$/) in this same component. It was opened 2024-09-17, sat for fourteen months, and was closed unmerged on 2025-12-01 with no comment, so it is not clear whether it was rejected on the merits or swept as stale. If kebab/snake case is wanted, that patch is the whole change.
  • If the restriction is deliberate, say so in the UI. Show validation text ("Folder names may contain letters and numbers only") and let the character be typed and rejected, rather than swallowing the keystroke. A silent filter is indistinguishable from a broken input.

Whichever you choose, it would also be worth deciding whether the API should enforce the same rule — right now the two layers disagree, and the API is the one that matches the data already in production environments.

Screenshots

Not applicable — the bug is the absence of any visible response.

Platform you are having the issue on:

Phase Cloud (console at phase.dev), Chrome on Linux. CLI phase 2.3.0 for the workaround.

Additional context

Workaround, for anyone who lands here: skip the folder dialog entirely and let a path-scoped write create the folder.

phase secrets import secrets.env --app <app> --env <env> --path /workers/user-service --type secret

Nested paths work too — create_environment_folder_structure walks each segment with get_or_create, so an existing parent is reused and only the missing leaf is created. Note that --type applies to the whole file, so config-typed and secret-typed values need one import each.

Activity

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

Metadata

Metadata

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