# Merge pull request #1738 from entireio/fix/1398-hook-stdin-hang-windows

`aacb4d6`→[main](/content/gh/entireio/cli/commits/main/index.html)

Fix agent hooks hanging on stdin EOF on Windows/Git Bash (#1398)

## Changes

- cmd/entire/cli  
  - agent  
    - copilotcli  
      - Mlifecycle.go+5/-3
    - Mevent.go+61/-8
    - Aevent_test.go+127
    - external  
      - Mexternal.go+7/-2
    - pi  
      - Mlifecycle.go+5/-11
  - Mhooks.go+6/-18

```go
168 unmodified lines

// Stream one JSON value rather than io.ReadAll so the hook never blocks
// waiting for stdin EOF that some agents don't send on Windows (issue #1398).
func (c *CopilotCLIAgent) readHookEnvelope(stdin io.Reader) (*hookEnvelope, error) {

data, err := io.ReadAll(stdin)

if err != nil {
	return nil, fmt.Errorf("failed to read hook input: %w", err)
}

return parseHookEnvelope(data)
}
```

```go
func ReadAndParseHookInput[T any](stdin io.Reader) (*T, error) {

data, err := io.ReadAll(stdin)

if err != nil {
	return nil, fmt.Errorf("failed to read hook input: %w", err)
}

if len(data) == 0 {
	return nil, errors.New("empty hook input")
}

var result T
if err := json.Unmarshal(data, &result); err != nil {
	return nil, fmt.Errorf("failed to parse hook input: %w", err)
}
return &result, nil
}
```

## Test cases

### TestReadAndParseHookInput_ReturnsBeforeEOF

```go
func TestReadAndParseHookInput_ReturnsBeforeEOF(t *testing.T) {
	t.Parallel()

pr, pw := io.Pipe()
	go func() {
		if _, err := pw.Write([]byte(`{"session_id":"s1","transcript_path":"/t.jsonl"}`)); err != nil {
			_ = pw.CloseWithError(err)
		}
	}()

// ... rest of the test code
}
```

### TestReadHookInputRawLimited_ReturnsBeforeEOF

```go
func TestReadHookInputRawLimited_ReturnsBeforeEOF(t *testing.T) {
	// ... setup and execution
}
```

### TestReadAndParseHookInput_MalformedJSON

```go
func TestReadAndParseHookInput_MalformedJSON(t *testing.T) {
	_, err := ReadAndParseHookInput[hookInput](strings.NewReader(`{"session_id": INVALID}`))
	if err == nil {
			... handle error
	}
}
```

### Additional Functions

```go
func parseSubagentCheckpointHookInput(r io.Reader) (*SubagentCheckpointHookInput, error) {
	data, err := io.ReadAll(r)
	if err != nil {
		return nil, fmt.Errorf("failed to read input: %w", err)
	}

if len(data) == 0 {
		return nil, errors.New("empty input")
	}

var input SubagentCheckpointHookInput
	if err := json.Unmarshal(data, &input); err != nil {
		return nil, fmt.Errorf("failed to parse JSON: %w", err)
	}

return &input, nil
}
```
