You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[$175] BE appends a number on a distance rate name if you create a distance rate with name 0 #104778
Version Number: Reproducible in staging?: Needs Reproduction Reproducible in production?: Needs Reproduction If this was caught during regression testing, add the test name, ID and link from BrowserStack: Email or phone of affected tester (no customers): Logs:https://stackoverflow.com/c/expensify/questions/4856 Expensify/Expensify Issue URL: Issue reported by:@FitseTLT Slack conversation (hyperlinked to channel name): #Expensify Bugs
Action Performed:
Navigate Workspace settings > Distance rates
Click +Add rate
Enter name as 0 and any rate
Click "Save"
Observe the name of the added rate
Expected Result:
Rate name as 0
Actual Result:
BE appends a number on a distance rate name 0 1
Note:
The issue was reproduced by the reporter in only one workspace.
Workaround:
Unknown
Platforms:
Select the officially supported platforms where the issue was reproduced:
⚠️Preliminary analysis (low confidence): treat this as a starting point, not a conclusion.
The App sends the name 0 unchanged, so the server is what renames it to 0 1. My guess is that the server's duplicate-name check thinks 0 clashes with a rate name already in the reporter's workspace, then adds 1. That would explain why it only happens in one workspace.
I couldn't reproduce this on web with a new workspace. A rate named 0 saved and stayed 0, and a control rate Test also kept its name:
This appears to be a backend/internal issue. A separate internal investigation is needed.
DistanceRate.ts:146-208: the optimistic name is 0, and the request sends customUnitRate: JSON.stringify(...) with "name":"0". successData never writes a name, so 0 1 has to come from the server's Onyx update.
Please re-state the problem that we are trying to solve in this issue.
When a user adds a distance rate in Workspace settings > Distance rates and names it 0, the saved rate is shown as 0 1 instead of 0. The expected result is that the rate keeps exactly the name the user typed. The reporter saw this in only one workspace.
What is the root cause of that problem?
The App does not rename the rate. In CreateDistanceRatePage → createPolicyDistanceRate, values.rateName is a string, and "0" is truthy in JS. The optimistic rate is stored with name: "0". The 0 1 value comes from the server response, which overwrites the optimistic Onyx data.
The suffix is the backend's de-duplication behavior, which appends a counter when a rate name collides with an existing one. It fires for 0 for one of two reasons:
Falsy check on the name. A loose check such as PHP empty("0") or !$name treats "0" as "no name" or "name already taken", which sends it into the de-dupe path. In that case it should reproduce in any workspace.
Existing rate named 0. Since it reproduced in only one workspace, this is the more likely cause. That workspace probably already has a rate named 0, possibly disabled, deleted-pending or otherwise not visible in the list. The BE then de-dupes it. The App-side validation misses this case, so the user gets no feedback and the name changes silently after save.
I have not verified which of the two applies. Neither is a bug in the App's name handling.
What changes do you think we should make in order to solve the problem?
Confirm the cause (first step).
Check the CreateWorkspaceDistanceRate request payload and confirm it contains "name":"0".
Check the response Onyx update to confirm the BE returns "0 1".
Check whether the failing workspace already has a rate named 0, including disabled ones.
Backend fix (primary).
Replace truthiness and empty() checks on the rate name with strict checks ($name === '' or strlen(trim($name)) === 0) in the create-rate and unique-name logic.
Only append a counter if a rate with the same name actually exists.
App-side hardening (so the user is told instead of the name silently changing). In the create-rate validation, compare the entered name against all rates of the custom unit, including disabled ones, using a trimmed comparison:
// CreateDistanceRatePage.tsxconstvalidate=(values)=>{consterrors=ValidationUtils.getFieldRequiredErrors(values,[INPUT_IDS.RATE_NAME]);constname=values.rateName?.trim();constexists=Object.values(customUnit?.rates??{}).some((rate)=>rate.name?.trim()===name,// do NOT use `!!rate.name &&` style truthiness);if(name!==undefined&&name!==''&&exists){errors[INPUT_IDS.RATE_NAME]=translate('workspace.distanceRates.errors.rateNameExists');}// ...existing rate value validationreturnerrors;};
Also make sure no App code uses truthiness checks like if (!rate.name) or rate.name || fallback on rate names. Use === '' or ?? '' instead, so "0" is never treated as empty.
What alternative solutions did you explore? (Optional)
Strip the suffix on the client after the BE response. Rejected because it hides a server-side data inconsistency, and the stored name would still differ from what the server holds.
Block 0 as a rate name. Rejected because 0 is a valid name and the issue's expected result is that it is accepted.
Testing
Create a rate named 0 in a workspace with no rate named 0. It should save as 0 and stay 0 after refresh and after the server response.
In a workspace that already has a rate named 0 (enabled or disabled), try to create another 0. The App should show the duplicate-name error and send no request.
Create rates named 00, 0 (padded) and 0.5. They should behave consistently: saved as typed, or trimmed, with no counter appended.
Add a unit test for the validation function covering "0" with and without an existing "0" rate.
Regression: normal names, empty name (required error), and duplicate normal names behave as before.
changed the title [-]BE appends a number on a distance rate name if you create a distance rate with name 0[/-][+][$175] BE appends a number on a distance rate name if you create a distance rate with name 0[/+]on Oct 22, 2026
Please re-state the problem that we are trying to solve in this issue.
Al crear una tarifa de distancia con el nombre "0", el nombre termina como "0 1". Debemos conservar "0" cuando no exista un conflicto real de nombres, tanto después de guardar como después de recargar el workspace.
What is the root cause of that problem?
Evidencia verificable: el formulario construye la tarifa con name: values.name.trim(). La acción createPolicyDistanceRate() utiliza ese objeto para la actualización optimista y lo serializa mediante JSON.stringify(customUnitRate) para CreatePolicyDistanceRate. Ninguno de esos pasos añade " 1". Formulario, acción.
Causa primaria pendiente de confirmar: una decisión incorrecta en la generación o resolución de nombres del backend, si la petición contiene "0" y la respuesta o una lectura posterior devuelve "0 1". El título del issue por sí solo no demuestra esa frontera.
Una hipótesis es que el servidor confunda "0" con un nombre vacío. Por ejemplo, PHP considera falsa esa cadena; JavaScript la considera verdadera. Por tanto, atribuirlo a name || fallback en el frontend, sin una conversión adicional, sería incorrecto. Esto ilustra una hipótesis, no confirma el lenguaje ni la implementación del endpoint. PHP, JavaScript.
Posible causa secundaria: el estado particular del workspace podría activar una colisión real o una comparación incorrecta. Que solo se reproduzca en un workspace obliga a comprobar sus tarifas, incluidas las deshabilitadas o eliminadas que participen en la comprobación de unicidad.
No he reproducido el caso ni inspeccionado el backend; presentar una línea concreta como culpable sería especulativo.
What changes do you think we should make in order to solve the problem?
El diff mínimo defendible debe modificar únicamente el predicado defectuoso en el backend, una vez identificado:
Capturar customUnitRate.name enviado, nombre recibido y nombre persistido, correlacionados por customUnitRateID.
Confirmar que no existe una tarifa que legítimamente reserve "0".
Localizar la condición que activa el nombre automático o el sufijo.
Si se confirma que el backend utiliza un chequeo booleano para detectar ausencia, sustituirlo por una comprobación explícita que preserve "0". El cambio conceptual sería:
- nameIsMissing = booleanEmptyCheck(name)+ nameIsMissing = name is absent OR name is null OR name === ""
Este fragmento es pseudocódigo, no un parche extraído del repositorio. Debe conservar las reglas existentes de normalización y validación.
Si el fallo está en la detección de duplicados, corregir exclusivamente esa comparación según el contrato de nombres, preservando la resolución de conflictos reales.
No hay evidencia que justifique un cambio funcional en ReactNativeChat. Si se confirma la mutación en el servidor, la implementación corresponde a quien tenga acceso al repositorio afectado; no sería honesto prometer un parche completo desde el código público.
What alternative solutions did you explore? (Optional)
No se proporcionó una propuesta de MelvinBot, por lo que no es posible atribuirle fallas específicas.
Descartaría estas alternativas:
Prohibir "0": contradice el resultado esperado.
Cambiar || por ?? en el cliente sin identificar la condición:"0" ya es verdadero en JavaScript.
Enviar caracteres invisibles o añadir espacios: altera el nombre y puede afectar su unicidad.
Restaurar "0" en Onyx o renombrar después de crear: oculta el defecto, añade solicitudes y permite discrepancias con el servidor.
El criterio para evaluar cualquier propuesta de MelvinBot sería demostrar dónde aparece por primera vez el sufijo y corregir esa decisión con el menor cambio.
What automated tests can you add to prevent this from re-occurring?
Añadir un test unitario en la función del backend que decide el nombre final:
Given: existing names = ["Standard", "0 1"], with "0" available
When: resolving the requested name "0"
Then: the result is exactly "0"
Parametrizarlo con "00", "1" y un nombre alfabético; cubrir además nombre vacío y un duplicado real conforme al contrato existente.
Complementarlo con una prueba del endpoint: crear una tarifa llamada "0" y verificar ese nombre tanto en la respuesta como en una lectura posterior del workspace.
Un test del cliente que compruebe el payload sería útil como prueba de contrato, pero no impediría una regresión del backend. Los tests anteriores están propuestos; no se han ejecutado.
Activity
MelvinBot commentedon Oct 22, 2026
The App sends the name
0unchanged, so the server is what renames it to0 1. My guess is that the server's duplicate-name check thinks0clashes with a rate name already in the reporter's workspace, then adds1. That would explain why it only happens in one workspace.I couldn't reproduce this on web with a new workspace. A rate named
0saved and stayed0, and a control rateTestalso kept its name:This appears to be a backend/internal issue. A separate internal investigation is needed.
Investigation details
Frontend (ruled out)
CreateDistanceRatePage.tsx:74-84builds the rate withname: values.name.trim()and has no fallback name.DistanceRate.ts:146-208: the optimistic name is0, and the request sendscustomUnitRate: JSON.stringify(...)with"name":"0".successDatanever writes a name, so0 1has to come from the server's Onyx update.PolicyDistanceRatesUtils.ts:46-68only rejects exact duplicate names. It never renames.Why "falsy
0" alone doesn't fit: if the server treated0as an empty name everywhere, it would have reproduced on a new workspace too. It didn't.Related open issue: [Exploratory] Distance-Add rate with the name "Rate" after added the "Rate 1", the name change to "Rate 1". The server renamed a non-duplicate name there too, which points to the same duplicate-name logic.
To confirm: an internal engineer should:
CreatePolicyDistanceRaterequest from 2026-10-22.Next Steps: Reply with
@MelvinBot <your feedback>to refine this analysis.view run · view recording
Abdallah-1551 commentedon Oct 22, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
When a user adds a distance rate in Workspace settings > Distance rates and names it
0, the saved rate is shown as0 1instead of0. The expected result is that the rate keeps exactly the name the user typed. The reporter saw this in only one workspace.What is the root cause of that problem?
The App does not rename the rate. In
CreateDistanceRatePage→createPolicyDistanceRate,values.rateNameis a string, and"0"is truthy in JS. The optimistic rate is stored withname: "0". The0 1value comes from the server response, which overwrites the optimistic Onyx data.The suffix is the backend's de-duplication behavior, which appends a counter when a rate name collides with an existing one. It fires for
0for one of two reasons:empty("0")or!$nametreats"0"as "no name" or "name already taken", which sends it into the de-dupe path. In that case it should reproduce in any workspace.0. Since it reproduced in only one workspace, this is the more likely cause. That workspace probably already has a rate named0, possibly disabled, deleted-pending or otherwise not visible in the list. The BE then de-dupes it. The App-side validation misses this case, so the user gets no feedback and the name changes silently after save.I have not verified which of the two applies. Neither is a bug in the App's name handling.
What changes do you think we should make in order to solve the problem?
Confirm the cause (first step).
CreateWorkspaceDistanceRaterequest payload and confirm it contains"name":"0"."0 1".0, including disabled ones.Backend fix (primary).
empty()checks on the rate name with strict checks ($name === ''orstrlen(trim($name)) === 0) in the create-rate and unique-name logic.App-side hardening (so the user is told instead of the name silently changing). In the create-rate validation, compare the entered name against all rates of the custom unit, including disabled ones, using a trimmed comparison:
Also make sure no App code uses truthiness checks like
if (!rate.name)orrate.name || fallbackon rate names. Use=== ''or?? ''instead, so"0"is never treated as empty.What alternative solutions did you explore? (Optional)
0as a rate name. Rejected because0is a valid name and the issue's expected result is that it is accepted.Testing
0in a workspace with no rate named0. It should save as0and stay0after refresh and after the server response.0(enabled or disabled), try to create another0. The App should show the duplicate-name error and send no request.00,0(padded) and0.5. They should behave consistently: saved as typed, or trimmed, with no counter appended."0"with and without an existing"0"rate.melvin-bot commentedon Oct 22, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @FitseTLT (
External)[-]BE appends a number on a distance rate name if you create a distance rate with name 0[/-][+][$175] BE appends a number on a distance rate name if you create a distance rate with name 0[/+]melvin-bot commentedon Oct 22, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022113317761355064427
FitseTLT commentedon Oct 22, 2026
This is a BE bug for only my workspace-id
F3B3FA50B68D7726it is oddly appending numbers to distance rate name when I save the name as0🎀 👀 🎀 C+ reviewed
melvin-bot commentedon Oct 22, 2026
Triggered auto assignment to @francoisl, see https://stackoverflow.com/c/expensify/questions/7972 for more details.
camesenin commentedon Oct 22, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
Al crear una tarifa de distancia con el nombre
"0", el nombre termina como"0 1". Debemos conservar"0"cuando no exista un conflicto real de nombres, tanto después de guardar como después de recargar el workspace.What is the root cause of that problem?
Evidencia verificable: el formulario construye la tarifa con
name: values.name.trim(). La accióncreatePolicyDistanceRate()utiliza ese objeto para la actualización optimista y lo serializa medianteJSON.stringify(customUnitRate)paraCreatePolicyDistanceRate. Ninguno de esos pasos añade" 1". Formulario, acción.Causa primaria pendiente de confirmar: una decisión incorrecta en la generación o resolución de nombres del backend, si la petición contiene
"0"y la respuesta o una lectura posterior devuelve"0 1". El título del issue por sí solo no demuestra esa frontera.Una hipótesis es que el servidor confunda
"0"con un nombre vacío. Por ejemplo, PHP considera falsa esa cadena; JavaScript la considera verdadera. Por tanto, atribuirlo aname || fallbacken el frontend, sin una conversión adicional, sería incorrecto. Esto ilustra una hipótesis, no confirma el lenguaje ni la implementación del endpoint. PHP, JavaScript.Posible causa secundaria: el estado particular del workspace podría activar una colisión real o una comparación incorrecta. Que solo se reproduzca en un workspace obliga a comprobar sus tarifas, incluidas las deshabilitadas o eliminadas que participen en la comprobación de unicidad.
No he reproducido el caso ni inspeccionado el backend; presentar una línea concreta como culpable sería especulativo.
What changes do you think we should make in order to solve the problem?
El diff mínimo defendible debe modificar únicamente el predicado defectuoso en el backend, una vez identificado:
customUnitRate.nameenviado, nombre recibido y nombre persistido, correlacionados porcustomUnitRateID."0".Si se confirma que el backend utiliza un chequeo booleano para detectar ausencia, sustituirlo por una comprobación explícita que preserve
"0". El cambio conceptual sería:Este fragmento es pseudocódigo, no un parche extraído del repositorio. Debe conservar las reglas existentes de normalización y validación.
Si el fallo está en la detección de duplicados, corregir exclusivamente esa comparación según el contrato de nombres, preservando la resolución de conflictos reales.
No hay evidencia que justifique un cambio funcional en ReactNativeChat. Si se confirma la mutación en el servidor, la implementación corresponde a quien tenga acceso al repositorio afectado; no sería honesto prometer un parche completo desde el código público.
What alternative solutions did you explore? (Optional)
No se proporcionó una propuesta de MelvinBot, por lo que no es posible atribuirle fallas específicas.
Descartaría estas alternativas:
"0": contradice el resultado esperado.||por??en el cliente sin identificar la condición:"0"ya es verdadero en JavaScript."0"en Onyx o renombrar después de crear: oculta el defecto, añade solicitudes y permite discrepancias con el servidor.El criterio para evaluar cualquier propuesta de MelvinBot sería demostrar dónde aparece por primera vez el sufijo y corregir esa decisión con el menor cambio.
What automated tests can you add to prevent this from re-occurring?
Añadir un test unitario en la función del backend que decide el nombre final:
Parametrizarlo con
"00","1"y un nombre alfabético; cubrir además nombre vacío y un duplicado real conforme al contrato existente.Complementarlo con una prueba del endpoint: crear una tarifa llamada
"0"y verificar ese nombre tanto en la respuesta como en una lectura posterior del workspace.Un test del cliente que compruebe el payload sería útil como prueba de contrato, pero no impediría una regresión del backend. Los tests anteriores están propuestos; no se han ejecutado.
Contributor details
Your Expensify account email: camesenin@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~01b60d704ba377be83
melvin-bot commentedon Oct 22, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!