{"record":{"id":"e6ec4424d4a6b5dc","repo":"chenhg5/cc-connect","slug":"send-permission-request-w","errorCode":null,"errorMessage":"send permission request: %w","messagePattern":"send permission request: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/antigravityhook/protocol.go","lineNumber":58,"sourceCode":"\t}\n\tif len(input) > maxHookInput {\n\t\treturn fmt.Errorf(\"hook input exceeds %d bytes\", maxHookInput)\n\t}\n\tif !json.Valid(input) {\n\t\treturn fmt.Errorf(\"hook input is not valid JSON\")\n\t}\n\n\tconn, err := net.DialTimeout(\"tcp\", address, bridgeDialTimeout)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"connect permission bridge: %w\", err)\n\t}\n\tdefer func() { _ = conn.Close() }()\n\t// The listener is started before agy runs this hook, so dial failures should\n\t// fail closed quickly. After connect, wait much longer for a human response.\n\t_ = conn.SetDeadline(time.Now().Add(bridgeResponseTimeout))\n\n\tif err := json.NewEncoder(conn).Encode(BridgeRequest{Token: token, HookInput: input}); err != nil {\n\t\treturn fmt.Errorf(\"send permission request: %w\", err)\n\t}\n\n\tvar response BridgeResponse\n\tif err := json.NewDecoder(io.LimitReader(conn, 64<<10)).Decode(&response); err != nil {\n\t\treturn fmt.Errorf(\"read permission response: %w\", err)\n\t}\n\tswitch response.Decision {\n\tcase \"allow\", \"deny\":\n\tdefault:\n\t\treturn fmt.Errorf(\"invalid permission decision %q\", response.Decision)\n\t}\n\n\tif err := json.NewEncoder(out).Encode(response); err != nil {\n\t\treturn fmt.Errorf(\"write hook response: %w\", err)\n\t}\n\treturn nil\n}\n","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/antigravityhook/protocol.go#L40-L76","documentation":"Relay wraps json.Encoder failures when writing the BridgeRequest (token + hook input) to the bridge TCP connection: 'send permission request: %w'. Since the connection was just established, this usually means the connection broke between dial and write — most often the bridge closed it (e.g. token rejected) or a network/socket error occurred.","triggerScenarios":"json.NewEncoder(conn).Encode returns err — connection reset by peer, bridge listener closed the socket after a failed token check, deadline exceeded, or fd error mid-write.","commonSituations":"Mismatched/expired CC_CONNECT_AGY_PERMISSION_TOKEN causing the bridge to drop the connection; bridge goroutine crashed while the hook was connecting; race with session teardown.","solutions":["Check cc-connect logs for why the bridge closed the connection (likely token mismatch)","Confirm CC_CONNECT_AGY_PERMISSION_TOKEN matches the token the session generated","Inspect the wrapped %w error (e.g. 'broken pipe' vs 'connection reset') to pick the cause","Ensure the session isn't being stopped concurrently with the permission request","Retry the permission-triggering action once the bridge is confirmed healthy"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// verify token before invoking\nif tok != expectedSessionToken { log.Fatal(\"CC_CONNECT_AGY_PERMISSION_TOKEN mismatch\") }","typeGuard":null,"tryCatchPattern":"if err := Relay(...); err != nil && strings.HasPrefix(err.Error(), \"send permission request:\") {\n    log.Printf(\"bridge dropped connection (token or teardown?): %v\", err)\n    // fail closed = deny\n}","preventionTips":["Keep address and token env vars from the same session generation — never mix old tokens with new listeners","Check cc-connect logs for bridge-side close reasons","Avoid stopping the session while a permission decision is pending"],"tags":["network","json","permissions","socket"],"backgroundTag":"broken-pipe","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}