{"record":{"id":"9d880cc80f5dba2f","repo":"chenhg5/cc-connect","slug":"codex-app-server-no-pending-approval-for-request","errorCode":null,"errorMessage":"codex app-server: no pending approval for request %s","messagePattern":"codex app-server: no pending approval for request (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"agent/codex/appserver_session.go","lineNumber":547,"sourceCode":"\t\tif err := os.WriteFile(fpath, img.Data, 0o644); err != nil {\n\t\t\treturn \"\", nil, fmt.Errorf(\"codex app-server: save image: %w\", err)\n\t\t}\n\t\timagePaths = append(imagePaths, fpath)\n\t}\n\n\tif strings.TrimSpace(prompt) == \"\" {\n\t\tprompt = \"Please analyze the attached image(s).\"\n\t}\n\n\treturn prompt, imagePaths, nil\n}\n\nfunc (s *appServerSession) RespondPermission(requestID string, result core.PermissionResult) error {\n\ts.approvalsMu.Lock()\n\tch := s.pendingApprovals[requestID]\n\ts.approvalsMu.Unlock()\n\tif ch == nil {\n\t\treturn fmt.Errorf(\"codex app-server: no pending approval for request %s\", requestID)\n\t}\n\tselect {\n\tcase ch <- result:\n\tdefault:\n\t}\n\treturn nil\n}\n\nfunc (s *appServerSession) handleServerRequest(probe map[string]json.RawMessage) {\n\trawID := probe[\"id\"]\n\tvar method string\n\tif err := json.Unmarshal(probe[\"method\"], &method); err != nil {\n\t\treturn\n\t}\n\tparams := probe[\"params\"]\n\n\tswitch method {\n\tcase \"item/commandExecution/requestApproval\", \"item/fileChange/requestApproval\":","sourceCodeStart":529,"sourceCodeEnd":565,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/codex/appserver_session.go#L529-L565","documentation":"RespondPermission looks up a pending approval channel by requestID in s.pendingApprovals; if no entry exists it returns this error. It means a permission decision arrived for a request the session is not currently waiting on — the request already resolved, expired, or the ID is wrong.","triggerScenarios":"Calling RespondPermission with a requestID that was never registered, or after the approval already completed, or after the session restarted and its in-memory pendingApprovals map was cleared.","commonSituations":"User double-clicks an approval button in the messaging platform so the second RespondPermission finds nothing; approval timed out and pendingApprovals was cleaned up; process restart between the request and the user's reply; passing the turn id instead of the approval request id.","solutions":["Use the exact requestID delivered in the approval request event","Treat this as benign when it's a duplicate click — check-and-ignore rather than surfacing an error to the user","Handle approvals within the timeout window before they are cleaned up","If it happens after restarts, persist/re-emit pending approval requests or invalidate stale buttons"],"exampleFix":"// before\nerr := sess.RespondPermission(requestID, core.PermissionResult{Approved: true})\nif err != nil { return err } // double-click now fails the handler\n// after\nif err := sess.RespondPermission(requestID, res); err != nil && !alreadyHandled(requestID) {\n    slog.Warn(\"approval already resolved\", \"requestID\", requestID)\n}","handlingStrategy":"type-guard","validationCode":"func hasPendingApproval(sess *appServerSession, requestID string) bool {\n    sess.approvalsMu.Lock(); defer sess.approvalsMu.Unlock()\n    return sess.pendingApprovals[requestID] != nil\n}","typeGuard":"if hasPendingApproval(sess, requestID) {\n    _ = sess.RespondPermission(requestID, res)\n} else {\n    slog.Warn(\"approval already resolved or unknown\", \"requestID\", requestID)\n}","tryCatchPattern":"if err := sess.RespondPermission(requestID, res); err != nil {\n    slog.Warn(\"respond permission: %v (likely duplicate/late reply)\", err) // do not fail the user flow\n}","preventionTips":["Disable approval buttons in the UI after the first click to prevent duplicate replies","Always carry the requestID from the original approval event, never the turn id","Invalidate stale approval prompts after session restarts","Set an approval timeout shorter than the platform's button lifetime"],"tags":["permissions","approval","state"],"backgroundTag":"resource-not-found","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}