Skip to content

fix: coerce stringified booleans in fpsProperties - #163

Closed
andrewwan-uipath wants to merge 3 commits into
mainfrom
fix/coerce-fps-bool-values
Closed

fix: coerce stringified booleans in fpsProperties#163
andrewwan-uipath wants to merge 3 commits into
mainfrom
fix/coerce-fps-bool-values

Conversation

@andrewwan-uipath

Copy link
Copy Markdown
Contributor

Problem

UiPathRuntimeContext.from_config maps fpsProperties onto the context with setattr, which bypasses pydantic validation (model_config has no validate_assignment). When a producer delivers fpsProperties as a string→string map, a JSON boolean arrives as text and is stored raw on a bool-typed field.

"false" is a non-empty string, so it is truthy. Every guard reading the field inverts, with no error and no log.

The visible case is conversationalService.endExchange. Setting it to false is meant to leave the exchange open; with "false" stored, if not self.end_exchange: never fires and the runtime emits the exchange-end event anyway, closing an exchange the caller explicitly asked to keep open.

Three fps keys map onto bool fields and share the failure mode:

key field
conversationalService.endExchange end_exchange
conversationalService.enableOutputs conversational_outputs_enabled
conversationalService.runAsMe conversational_run_as_me

Booleans intended to be true were unaffected — "true" is truthy too — which is why this went unnoticed. Only a value meant to be false exposes it.

Fix

Parse stringified booleans in the fps loop when the target field is annotated bool. Anything unrecognizable logs a warning and keeps the field default rather than guessing.

Deliberately scoped to the fps loop rather than enabling validate_assignment=True: that flag is model-wide, and this model is assigned to loosely in ~12 places across from_env/from_config with extra="allow" set, so turning on assignment validation risks unrelated breakage.

"" is treated as falsy alongside "false"/"0"/"no"/"off", so an empty fps value can't read as an opt-in.

Verification

Real JSON booleans keep working unchanged, and non-bool targets keep their raw value (conversationId: "false" stays the string "false").

Tests written first and observed failing for the right reason (raw strings landing on bool fields) before the implementation. Full suite green; ruff check, ruff format --check and mypy clean.

Note on the upstream cause

The stringification itself is a transport bug being followed up separately with the Orchestrator team — its debug/JIT path appears to rebuild fpsProperties as string→string when it merges in debug.executor, so the same key arrives as a real boolean on non-debug runs and as a string on debug runs. This change makes the runtime robust either way, and stays correct once that is fixed.

🤖 Generated with Claude Code

from_config assigns fpsProperties onto the context with setattr, which
bypasses pydantic validation. Producers that deliver fpsProperties as a
string->string map therefore land "false" on a bool-typed field, where it
stays a truthy non-empty string and silently inverts the guards reading it.

conversationalService.endExchange is the visible case: "false" made the
runtime emit the exchange-end event anyway, closing an exchange the caller
asked to keep open. endExchange, enableOutputs and runAsMe all map onto
bool fields and share the failure mode; booleans meant to be true were
unaffected, so it went unnoticed.

Parse stringified booleans when the target field is annotated bool, and
warn and keep the default for anything unrecognizable rather than guessing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 20, 2026 18:25
@andrewwan-uipath
andrewwan-uipath requested review from a team and cristipufu as code owners August 20, 2026 18:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens UiPathRuntimeContext.from_config against producers that emit fpsProperties as a string→string map, ensuring bool-typed context fields don’t silently receive truthy non-empty strings like "false" and invert runtime guards.

Changes:

  • Add a small bool-string parser and apply it during fpsProperties → context attribute assignment when the target field is annotated as bool.
  • Log a warning and keep the field default when a would-be boolean string is unrecognizable (rather than guessing).
  • Add focused tests covering truthy/falsy string spellings, non-bool fps passthrough, unparseable values, and real JSON booleans; bump package version.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/uipath/runtime/context.py Adds _parse_bool_like and coerces stringified booleans when mapping fpsProperties onto bool fields.
tests/test_context.py Adds regression tests to ensure stringified boolean fps values are correctly handled and non-bool fields remain unchanged.
pyproject.toml Bumps version to 0.13.2.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread src/uipath/runtime/context.py Fixed
@andrewwan-uipath
andrewwan-uipath marked this pull request as draft August 20, 2026 18:36
Only "true", "false" and "" are parsed. "0"/"1"/"yes"/"no"/"on"/"off" are
spellings a JSON serializer never emits for a boolean, so coercing them was
guessing at intent; unrecognized values now pass through with the behavior
they have always had instead.

Also drop the fps value from the warning. It is external input, and
SonarCloud flagged interpolating it as a log-injection risk. The key and
target attribute come from the internal mapping table and are enough to
diagnose.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants