A little investigation of the VCL source showed this, which is the main thread proc:

[pascal]function ThreadProc(Thread: TThread): Integer;
var
FreeThread: Boolean;
begin
try
if not Thread.Terminated then
try
Thread.Execute;
except
Thread.FFatalException := AcquireExceptionObject;
end;
finally
FreeThread := Thread.FFreeOnTerminate;
Result := Thread.FReturnValue;
Thread.FFinished := True;
Thread.DoTerminate;
if FreeThread then Thread.Free;
EndThread(Result);
end;
end;[/pascal]
It executes the TThread.Execute method then does the clearing up later, which is the interesting part. The DoTerminate is called using synchonize:

[pascal]procedure TThread.CallOnTerminate;
begin
if Assigned(FOnTerminate) then FOnTerminate(Self);
end;

procedure TThread.DoTerminate;
begin
if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);
end;[/pascal]

Since it has the synchonize call there, it should be safe. One suggestion: what happens if you change your OnTerminate to look like this:

[pascal]procedure TFileSearch.OnTerminate(Sender: TObject);
begin
if Assigned(OnCompletion) then OnCompletion(Self);
FreeAndNil(SearchThread);
end; [/pascal]

?

Does your thread have FreeOnTerminate set to true?
Do you suspend your thread at the end of your execute (this could probably spell bad news if you free it before it gets resumed, not sure)?

I'll try to get an example of this running safely tomorrow (time for bed). Off-hand, I couldn't say what's blowing up the app.