phacility/phabricator · error · Exception
Expected "%s" property to contain a dictionary.
Error message
Expected "%s" property to contain a dictionary.
What it means
The Buildkite webhook endpoint (reached at harbormaster/buildkite/) decodes the raw JSON body with phutil_json_decode, accepts only event == 'build.finished', and then requires the top-level 'build' property to be an array (PHP dictionary). If 'build' is absent (idx returns null) or a scalar (string, number), this plain Exception is thrown and Buildkite receives a 500 for the webhook delivery.
Source
Thrown at src/applications/harbormaster/controller/HarbormasterBuildkiteHookController.php:24
public function shouldRequireLogin() {
return false;
}
/**
* @phutil-external-symbol class PhabricatorStartup
*/
public function handleRequest(AphrontRequest $request) {
$raw_body = PhabricatorStartup::getRawInput();
$body = phutil_json_decode($raw_body);
$event = idx($body, 'event');
if ($event != 'build.finished') {
return $this->newHookResponse(pht('OK: Ignored event.'));
}
$build = idx($body, 'build');
if (!is_array($build)) {
throw new Exception(
pht(
'Expected "%s" property to contain a dictionary.',
'build'));
}
$meta_data = idx($build, 'meta_data');
if (!is_array($meta_data)) {
throw new Exception(
pht(
'Expected "%s" property to contain a dictionary.',
'build.meta_data'));
}
$target_phid = idx($meta_data, 'buildTargetPHID');
if (!$target_phid) {
return $this->newHookResponse(pht('OK: No Harbormaster target PHID.'));
}
View on GitHub (pinned to 5720a38cfe)
Solutions
- Send the full, unmodified Buildkite 'build.finished' webhook payload, which always contains a 'build' object.
- Verify with curl that your test body includes a JSON object under 'build' (e.g. "build": {"state":"passed", ...}).
- Make sure only Buildkite is configured to deliver to this endpoint and nothing rewrites the body.
- If you maintain an integration, pre-validate the payload shape (json object at build) before forwarding.
Example fix
// before (sender)
curl -d '{"event":"build.finished","state":"passed"}' https://phabricator/harbormaster/buildkite/
// after (sender) - include the build dictionary
curl -d '{"event":"build.finished","build":{"state":"passed","meta_data":{"buildTargetPHID":"PHID-HMBT-xxx"}}}' https://phabricator/harbormaster/buildkite/ Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-validate a payload before delivering it to the hook (test harness):
$body = phutil_json_decode($raw);
if (idx($body, 'event') !== 'build.finished') { return; }
if (!is_array(idx($body, 'build'))) {
// do not send / fix payload shape first
} Type guard
function isBuildkiteFinishedPayload(array $body) {
return isset($body['event'])
&& $body['event'] === 'build.finished'
&& is_array($body['build'] ?? null);
} Try / catch
try {
return id(new HarbormasterBuildkiteHookController())->handleRequest($request);
} catch (Exception $e) {
// Return HTTP 400 to Buildkite instead of a raw 500 so the delivery is retryable
return new Aphront400Response();
} Prevention
- Always forward Buildkite's payload verbatim; never synthesize minimal payloads in production integrations.
- Point only Buildkite at this endpoint; route other providers to their own hooks.
- Add payload-shape assertions in integration tests (event, build object, meta_data object) before shipping webhook plumbing.
When it happens
Trigger: POSTing a payload like {"event":"build.finished"} or {"event":"build.finished","build":"finished"} to the hook URL; pointing a non-Buildkite CI system or a hand-rolled integration at this endpoint; a proxy or test harness mangling the JSON body; a future Buildkite API change that renames or restructures the build object.
Common situations: Testing the webhook with a minimal curl payload during setup; build.finished events from pipelines whose payload was customized; sending GitHub/other provider webhooks to the wrong URL.
Related errors
- Harbormaster build target "%s" does not exist.
- Harbormaster build target "%s" is not a Buildkite build step
- Buildkite request to target "%s" had the wrong authenticatio
- Build target ("%s") has the wrong type of build step. Only C
- This object does not support builds with Buildkite.
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/0aad79818168a963.
Report an issue: GitHub.