@@ -94,15 +94,59 @@ func (m *Manager) findOrInstall(tool string, importPath string) (string, error)
9494 return "" , fmt .Errorf ("installation of %s timed out after 2 minutes" , tool )
9595 }
9696
97- // Verify installation
98- if _ , err := os .Stat (toolPath ); err != nil {
99- return "" , fmt .Errorf ("%s was not installed at expected path %s" , tool , toolPath )
97+ // Verify installation and wait for the binary to be executable.
98+ // After go install completes, there can be a brief window where the file exists
99+ // but is still being written (causing "text file busy" errors on execution).
100+ if err := waitForExecutable (toolPath , 5 * time .Second ); err != nil {
101+ return "" , fmt .Errorf ("%s installation verification failed: %w" , tool , err )
100102 }
101103
102104 slog .Info ("Tool installed successfully" , "tool" , tool , "path" , toolPath )
103105 return toolPath , nil
104106}
105107
108+ // waitForExecutable waits until a binary file exists and can be opened for reading.
109+ // This handles the race condition where go install has written the file
110+ // but the OS hasn't fully released the write handle yet.
111+ func waitForExecutable (path string , timeout time.Duration ) error {
112+ deadline := time .Now ().Add (timeout )
113+ var lastErr error
114+
115+ for time .Now ().Before (deadline ) {
116+ // Check if file exists
117+ info , err := os .Stat (path )
118+ if err != nil {
119+ lastErr = err
120+ time .Sleep (50 * time .Millisecond )
121+ continue
122+ }
123+
124+ // Check if it's a regular file
125+ if ! info .Mode ().IsRegular () {
126+ lastErr = fmt .Errorf ("%s is not a regular file" , path )
127+ time .Sleep (50 * time .Millisecond )
128+ continue
129+ }
130+
131+ // Try to open the file for reading to verify it's not locked
132+ f , err := os .Open (path )
133+ if err != nil {
134+ lastErr = err
135+ time .Sleep (50 * time .Millisecond )
136+ continue
137+ }
138+ f .Close ()
139+
140+ // File is ready
141+ return nil
142+ }
143+
144+ if lastErr != nil {
145+ return fmt .Errorf ("timed out waiting for %s to be ready: %w" , path , lastErr )
146+ }
147+ return fmt .Errorf ("timed out waiting for %s to be ready" , path )
148+ }
149+
106150// getGOBIN returns the GOBIN directory for the project
107151func (m * Manager ) getGOBIN () (string , error ) {
108152 // First try to get GOBIN from go env
0 commit comments