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:
- 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.
- 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
- Open any app → environment → Create a Folder.
- Type
user-service in Folder name.
- The field shows
userservice; the hyphen never appears and no message is shown.
- 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.
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:
normalize_path_stringdoes no character validation at all (it only collapses//, adds a leading/and strips a trailing one), andcreate_environment_folder_structurecallsget_or_create(name=segment, …)with whatever segment string it is handed.POST /v1/secretsnormalises the path and calls that helper for any non-root path, so creating a secret at--path /workers/user-servicecreates the folder that the dialog refuses to name.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):(Minor aside while you are in there: the copy at L1007 is dead — shadowed by the identical one declared inside
handleUpdateName.)To Reproduce
user-servicein Folder name.userservice; the hyphen never appears and no message is shown.phase secrets create SOME_KEY --app <app> --env <env> --path /user-servicesucceeds and the folderuser-serviceappears in the console.Expected behavior
Either of these would resolve it — the first is preferable:
/^[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.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
phase2.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.
Nested paths work too —
create_environment_folder_structurewalks each segment withget_or_create, so an existing parent is reused and only the missing leaf is created. Note that--typeapplies to the whole file, so config-typed and secret-typed values need one import each.