{"record":{"id":"343bd6b2d3111cfb","repo":"OtterMind/Chat2DB","slug":"not-found","errorCode":null,"errorMessage":"not found","messagePattern":"not found","errorType":"http","errorClass":"RequestError","httpStatus":404,"severity":"warning","filePath":"script/github/qq_relay/relay_server.py","lineNumber":244,"sourceCode":"            raise RequestError(HTTPStatus.BAD_REQUEST, \"message must be non-empty text\")\n        if len(message) > config.max_message_length:\n            raise RequestError(HTTPStatus.BAD_REQUEST, \"message is too long\")\n        if CONTROL_CHARACTER_PATTERN.search(message):\n            raise RequestError(HTTPStatus.BAD_REQUEST, \"message contains control characters\")\n        return delivery_id, message\n\n    def do_GET(self) -> None:  # noqa: N802\n        if self.path == \"/healthz\":\n            self._send_json(HTTPStatus.OK, {\"ok\": True})\n            return\n        self._send_json(HTTPStatus.NOT_FOUND, {\"error\": \"not found\"})\n\n    def do_POST(self) -> None:  # noqa: N802\n        delivery_id = \"\"\n        reserved = False\n        try:\n            if self.path != \"/v1/qq/github\":\n                raise RequestError(HTTPStatus.NOT_FOUND, \"not found\")\n            self._authorize()\n            delivery_id, message = self._validate_payload(self._read_payload())\n            existing = self.relay_state.deliveries.reserve(delivery_id)\n            if existing is not None:\n                if existing == \"pending\":\n                    raise RequestError(\n                        HTTPStatus.SERVICE_UNAVAILABLE, \"delivery is still in progress\"\n                    )\n                self._send_json(\n                    HTTPStatus.OK,\n                    {\n                        \"ok\": True,\n                        \"duplicate\": True,\n                        \"message_id\": existing,\n                    },\n                )\n                return\n            reserved = True","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/OtterMind/Chat2DB/blob/5ee1e990e73fbcae1969dc554be254fedb3ab888/script/github/qq_relay/relay_server.py#L226-L262","documentation":"Raised at relay_server.py:244 (POST) and relay_server.py:237 (GET) for any path that is not an allowed route. The relay serves exactly two: POST /v1/qq/github and GET /healthz. Anything else returns HTTP 404 with {\"error\": \"not found\"}.","triggerScenarios":"POST to a typo'd or wrong path (/v1/github, /v1/qq, /qq/github), with a trailing slash (/v1/qq/github/), a doubled slash, or a wrong version (/v2/...); GET to /.","commonSituations":"Base URL misconfigured in the action/workflow; trailing slash added by urljoin or a proxy; endpoint renamed across a release; hitting the relay root to probe it.","solutions":["POST to exactly /v1/qq/github and GET health at /healthz.","Build the URL from a base with no trailing slash plus the literal path.","Confirm no proxy rewrites the path or strips/keeps a trailing slash."],"exampleFix":"# before\nrequests.post(f\"{base}/v1/qq/github/\", json=payload)\n# after\nrequests.post(f\"{base.rstrip(\"/\")}/v1/qq/github\", json=payload)","handlingStrategy":"validation","validationCode":"from urllib.parse import urljoin\nendpoint = urljoin(base.rstrip('/') + '/', 'v1/qq/github')\n# endpoint is now '<base>/v1/qq/github' with no trailing slash","typeGuard":"def is_relay_post_path(url: str) -> bool:\n    return url.rstrip('/').endswith('/v1/qq/github')","tryCatchPattern":"resp = requests.post(url, json=payload)\nif resp.status_code == 404:\n    url = url.rstrip('/') + '/v1/qq/github'\n    resp = requests.post(url, json=payload)","preventionTips":["Build the URL from a slash-trimmed base plus the literal '/v1/qq/github'.","GET /healthz first to confirm reachability before posting."],"tags":["http","routing","configuration","qq-relay"],"backgroundTag":null,"analyzedSha":"5ee1e990e73fbcae1969dc554be254fedb3ab888","analyzedAt":"2026-08-14T07:05:03.077Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}