{"record":{"id":"e0ac4b9647408601","repo":"phacility/phabricator","slug":"unknown-patch-s-in-s-expected-php-or-s","errorCode":null,"errorMessage":"Unknown patch \"%s\" in \"%s\", expected \".php\" or \".sql\" suffix.","messagePattern":"Unknown patch \"(.+?)\" in \"(.+?)\", expected \"\\.php\" or \"\\.sql\" suffix\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/infrastructure/storage/patch/PhabricatorSQLPatchList.php","lineNumber":23,"sourceCode":"  abstract public function getNamespace();\n  abstract public function getPatches();\n\n  /**\n   * Examine a directory for `.php` and `.sql` files and build patch\n   * specifications for them.\n   */\n  protected function buildPatchesFromDirectory($directory) {\n    $patch_list = Filesystem::listDirectory(\n      $directory,\n      $include_hidden = false);\n\n    sort($patch_list);\n    $patches = array();\n\n    foreach ($patch_list as $patch) {\n      $matches = null;\n      if (!preg_match('/\\.(sql|php)$/', $patch, $matches)) {\n        throw new Exception(\n          pht(\n            'Unknown patch \"%s\" in \"%s\", expected \".php\" or \".sql\" suffix.',\n            $patch,\n            $directory));\n      }\n\n      $patch_type = $matches[1];\n      $patch_full_path = rtrim($directory, '/').'/'.$patch;\n\n      $attributes = array();\n      if ($patch_type === 'php') {\n        $attributes = $this->getPHPPatchAttributes(\n          $patch,\n          $patch_full_path);\n      }\n\n      $patches[$patch] = array(\n        'type' => $patch_type,","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/infrastructure/storage/patch/PhabricatorSQLPatchList.php#L5-L41","documentation":"PhabricatorSQLPatchList::buildPatchesFromDirectory() found an entry in a patch directory that does not end in .sql or .php — the only suffixes the patch system accepts. Stray files (notes, backups, subdirectories) break patch discovery, and because the scan runs while building the whole patch map, every storage command fails, not just the one touching that patch.","triggerScenarios":"Any non-hidden file or directory in a patch directory whose name fails to match /\\.(sql|php)$/: for example notes.txt, foo.sql.orig, a .bak file, or a subdirectory. Hidden dotfiles are excluded, so .gitignore is safe.","commonSituations":"A developer leaving notes inside resources/sql/patches; editor or rsync backups dropped next to patch files; extracting an archive into the patches directory; adding a data file a PHP patch should read.","solutions":["Find the offender with `ls <patch-dir> | grep -v -E '\\.(sql|php)$'` — the error message already names the file and directory.","Delete the stray file or move it outside the patch directory.","If a file must remain, make it hidden (leading dot) — hidden entries are skipped by the scan."],"exampleFix":"# before: resources/sql/patches/ contains notes.txt\nmv resources/sql/patches/notes.txt .\n\n# after: only .sql/.php entries remain, the patch map builds again\n./bin/storage status","handlingStrategy":"validation","validationCode":"find resources/sql/patches -mindepth 1 -maxdepth 1 \\\n  ! -name '.*' ! -name '*.sql' ! -name '*.php' -print -quit \\\n  | grep -q . && { echo 'stray file in patch directory' >&2; exit 1; }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never keep notes or backups inside patch directories; use dot-names or move them out.","Add the CI guard above so a stray file fails the build, not the next deploy.","Treat patch directories as machine-read data, not scratch space."],"tags":["phabricator","bin-storage","patch","filesystem","patch-directory"],"backgroundTag":"invalid-patch-file","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}