Skip to content

fix(cli): preserve nested params in handler/settings JSON fields#2061

Merged
chubes4 merged 2 commits into
mainfrom
fix/2059-flow-update-params-corruption
May 17, 2026
Merged

fix(cli): preserve nested params in handler/settings JSON fields#2061
chubes4 merged 2 commits into
mainfrom
fix/2059-flow-update-params-corruption

Conversation

@chubes4
Copy link
Copy Markdown
Member

@chubes4 chubes4 commented May 17, 2026

Summary

wp datamachine flow update <flow_id> --step=<step_id> --handler-config=<json> was corrupting nested-array values in any handler/settings field declared with type: 'json' (today: the SystemTask params field). The corruption stored params == "" (empty string) in flow_step_settings, which FlowStepConfig::getPrimaryHandlerConfig() reads preferentially over handler_config, breaking every subsequent flow run with array_merge(): Argument #1 must be of type array, string given at SystemTaskStep.php:211.

Closes #2059

Root cause

SettingsHandler::sanitizeField() (inc/Core/Steps/Settings/SettingsHandler.php) had no case 'json': branch. json-typed fields fell through to default: sanitizeText(), which calls sanitize_text_field( wp_unslash( $value ) ). WordPress's sanitize_text_field() coerces array/object input to an empty string, so any nested-array params payload was silently zeroed out before storage.

Live proof of the coercion (run against data-machine@main):

$ wp eval '
require_once "inc/Core/Steps/Settings/SettingsHandler.php";
require_once "inc/Core/Steps/SystemTask/SystemTaskSettings.php";
var_dump(\DataMachine\Core\Steps\SystemTask\SystemTaskSettings::sanitize([
  "task" => "dispatch_message",
  "params" => ["channel"=>"x","recipient"=>"y","message"=>"z"]
]));
'
array(2) {
  ["task"]   => string(16) "dispatch_message"
  ["params"] => string(0) ""              ← BUG
}

After the fix, the same input returns params as the expected nested array verbatim.

File list

  • inc/Core/Steps/Settings/SettingsHandler.php — add case 'json': to the type switch and a new sanitizeJson() method that preserves nested arrays, decodes JSON-string payloads, and never returns a scalar string.
  • tests/flow-update-handler-config-params-smoke.php — pure-PHP smoke test pinning the contract across six input shapes.

Reproduction (main fails, branch passes)

Smoke test results, same file, two checkouts of the same branch — one with the SettingsHandler.php fix stashed away (== main), one with it in place:

# stashed (== main):  0 / 17 passed
$ git stash push -- inc/Core/Steps/Settings/SettingsHandler.php
$ php tests/flow-update-handler-config-params-smoke.php
[1] nested-array params is preserved verbatim:
  ✗ params remains an array after sanitize
  ✗ params is not coerced to empty string
  ✗ nested params keys round-trip verbatim
    expected: array(channel/recipient/message)
    actual:   ''
[2] JSON-string params is decoded into a nested array:
  ✗ JSON-string params decodes to array
  ✗ JSON-string params decodes to the same nested shape
[3] params is never a scalar string (regression guard for SystemTaskStep array_merge):
  ✗ array_merge(params, [...]) succeeds (case: nested array)
    — array_merge(): Argument #1 must be of type array, string given
  ✗ array_merge(params, [...]) succeeds (case: JSON string)
    — array_merge(): Argument #1 must be of type array, string given
  ... (all 6 cases fail the same way)

0 / 17 passed
# with fix:  17 / 17 passed
$ git stash pop
$ php tests/flow-update-handler-config-params-smoke.php
[1] nested-array params is preserved verbatim:
  ✓ params remains an array after sanitize
  ✓ params is not coerced to empty string
  ✓ nested params keys round-trip verbatim
[2] JSON-string params is decoded into a nested array:
  ✓ JSON-string params decodes to array
  ✓ JSON-string params decodes to the same nested shape
[3] params is never a scalar string (regression guard for SystemTaskStep array_merge):
  ✓ params is an array + array_merge succeeds (6 cases × 2 asserts each)

17 / 17 passed

Regression sweep

Ran every smoke that touches SystemTask, FlowStep config, or the flow-update CLI path:

  • tests/dispatch-message-task-smoke.php — 46/46 pass
  • tests/flow-update-set-user-message-smoke.php — 35/35 pass
  • tests/systemtask-passthrough-smoke.php — 29/29 pass
  • tests/system-task-config-passthrough-smoke.php — 36/36 pass
  • tests/system-run-task-params-smoke.php — 28/28 pass
  • tests/flow-step-config-factory-smoke.php — 6/13 pass (pre-existing, fails identically on main; unrelated to this change)

phpcs clean on both changed files.

Scope discipline

  • No CHANGELOG.md edits, no version bumps — homeboy owns those.
  • No homeboy release, no homeboy deploy — PR only.
  • The fix lives in the base SettingsHandler (right layer) instead of patching SystemTaskSettings, so every future handler that declares a json field gets the same correct behaviour for free.

cc <@532385681268408341>

homeboy-ci Bot added 2 commits May 17, 2026 20:18
SettingsHandler::sanitizeField() had no `json` case, so json-typed fields
fell through to the default text-sanitization branch. sanitize_text_field()
coerces array inputs to an empty string, which corrupted the SystemTask
`params` field whenever `wp datamachine flow update --handler-config` was
called with a nested-array payload like
`{"task":"dispatch_message","params":{"channel":"x",...}}`.

The corrupt empty string was then stored in flow_step_settings.params,
which FlowStepConfig::getPrimaryHandlerConfig() reads preferentially over
handler_config.params. SystemTaskStep::executeStep() then fataled with
`array_merge(): Argument #1 must be of type array, string given`.

Add a dedicated sanitizeJson() that:
  - returns nested arrays verbatim,
  - decodes JSON-encoded strings into associative arrays,
  - falls back to the schema default (decoded if needed) or an empty
    array on invalid input,
  - never returns a scalar string.

Closes #2059
Smoke test for the regression fixed in the previous commit. Asserts:

  1. SettingsHandler::sanitize() preserves nested-array `params` verbatim
     instead of coercing it to an empty string.
  2. JSON-string `params` decodes into the same nested-array shape.
  3. `params` is never returned as a scalar string across six input
     shapes (nested array, JSON string, missing key, empty array,
     invalid JSON, empty string), so downstream array_merge() in
     SystemTaskStep cannot fatal.

17/17 assertions pass on the fix branch; 0/17 pass without the fix
(documenting the exact pre-fix coercion failure modes).

Run with: php tests/flow-update-handler-config-params-smoke.php
@homeboy-ci
Copy link
Copy Markdown
Contributor

homeboy-ci Bot commented May 17, 2026

Homeboy Results — data-machine

Lint

lint — passed

ℹ️ Full options: homeboy docs commands/lint
Deep dive: homeboy lint data-machine --changed-since 6251ed9

Test

test — passed

ℹ️ Auto-fix lint issues: homeboy refactor data-machine --from lint --write
ℹ️ Collect coverage: homeboy test data-machine --coverage
ℹ️ Pass args to test runner: homeboy test -- [args]
ℹ️ Full options: homeboy docs commands/test
Deep dive: homeboy test data-machine --changed-since 6251ed9

Audit

audit — passed

  • dead_code — 2 finding(s)
  • requested_detectors — 1 finding(s)
  • Total: 3 finding(s)

Deep dive: homeboy audit data-machine --changed-since 6251ed9

Tooling versions
  • Homeboy CLI: homeboy 0.182.0+59845940
  • Extension: wordpress from https://github.com/Extra-Chill/homeboy-extensions
  • Extension revision: dd47f26a
  • Action: unknown@unknown

@chubes4 chubes4 merged commit 9bfee14 into main May 17, 2026
5 checks passed
@chubes4 chubes4 deleted the fix/2059-flow-update-params-corruption branch May 17, 2026 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant