AlistGo/alist · error
expected *os.File, got %T
Error message
expected *os.File, got %T
What it means
PutResult calls file.CacheFullInTempFile(), which returns model.File — an interface, not a concrete *os.File. Per internal/stream/stream.go, the returned value is either the stream's pre-set *os.File tmp file, OR the stream's original Reader when that Reader already implements model.File (e.g. a driver-supplied seekable file or an in-memory File implementation). The unconditional type assertion tempFile.(*os.File) fails for any non-*os.File implementation.
Source
Thrown at drivers/mediafire/driver.go:348
return nil
}
func (d *Mediafire) Put(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) error {
_, err := d.PutResult(ctx, dstDir, file, up)
return err
}
func (d *Mediafire) PutResult(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
tempFile, err := file.CacheFullInTempFile()
if err != nil {
return nil, err
}
defer tempFile.Close()
osFile, ok := tempFile.(*os.File)
if !ok {
return nil, fmt.Errorf("expected *os.File, got %T", tempFile)
}
fileHash, err := d.calculateSHA256(osFile)
if err != nil {
return nil, err
}
checkResp, err := d.uploadCheck(ctx, file.GetName(), file.GetSize(), fileHash, dstDir.GetID())
if err != nil {
return nil, err
}
if checkResp.Response.ResumableUpload.AllUnitsReady == "yes" {
up(100.0)
}
if checkResp.Response.HashExists == "yes" && checkResp.Response.InAccount == "yes" {
up(100.0)View on GitHub (pinned to 843d9dc814)
Solutions
- Change calculateSHA256 to accept io.Reader (SHA-256 needs only sequential reads) and drop the *os.File requirement entirely
- Alternatively use the model.File interface and only Rewind/Seek when it exposes it
- If an *os.File is truly required (e.g. for upload resumption), wrap non-*os.File values by copying to a temp file first
Example fix
// before: hard type assertion on an interface return
osFile, ok := tempFile.(*os.File)
if !ok {
return nil, fmt.Errorf("expected *os.File, got %T", tempFile)
}
fileHash, err := d.calculateSHA256(osFile)
// after: hash any reader, no assertion needed
if seeker, ok := tempFile.(io.Seeker); ok {
_, _ = seeker.Seek(0, io.SeekStart)
}
fileHash, err := d.calculateSHA256(tempFile) // signature: (r io.Reader) (string, error) Defensive patterns
Strategy: type-guard
Type guard
// accept any model.File; only seek when possible
func toHashReader(f model.File) io.Reader {
if s, ok := f.(io.Seeker); ok {
_, _ = s.Seek(0, io.SeekStart)
}
return f
} Try / catch
Replace the panic-prone unconditional assertion with the two-value form and a graceful path: if osFile, ok := tempFile.(*os.File); ok { use it } else { rewind via io.Seeker and hash the reader directly } — the hash never needed *os.File. Prevention
- Never hard-assert concrete types on interface returns from framework APIs (CacheFullInTempFile returns model.File)
- Write helpers like calculateSHA256 against io.Reader, the narrowest interface you need
- Test uploads with mock FileStreamers that return non-*os.File model.File implementations
When it happens
Trigger: Uploading via a FileStreamer whose Reader is a custom model.File (in-memory buffer, another driver's file handle, or a SeekableStream wrapping a non-temp reader) — CacheFullInTempFile returns it as-is without copying to an *os.File, and the assertion panics path fails.
Common situations: Server-side copy between storages, or upload pipelines where the stream originates from another driver rather than an HTTP multipart upload; also unit tests with mock FileStreamers.
Related errors
- failed to get action token: %w
- MediaFire upload check failed: %s
- failed to read response body: %v
- failed to parse response: %v
- resumable upload failed with status %d
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/be8f4b1e3ce43328.
Report an issue: GitHub.