HTTP File Downloader (javascript, written by Claude Code)
envgap__claude-code__javascript-t1-24
Written by a coding agent; not on GitHubWritten 2026-02-27
01 / FAILURE SIGNATURE
As the study recorded it
No identifying execution failure has been captured.
Not a benchmark task.
- The project already builds and runs before the fix, so there is nothing to repair.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
package.json- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
claude-code/javascript-t1 #24 · read the task the agent was given
Claude Code wrote this javascript project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: HTTP File Downloader Write a program that downloads files from HTTP/HTTPS URLs with support for resumable downloads, progress tracking, concurrent downloads, and checksum verification. FUNCTIONAL REQUIREMENTS: - Accept one or more URLs as command-line arguments - Display a progress bar during download showing: percentage complete, bytes downloaded, total size, download speed, and estimated time remaining - Support resumable downloads via HTTP Range headers: if a download is interrupted, restarting with the same URL and output path should resume from where it stopped via --resume flag - Support concurrent downloading of multiple files via --parallel flag with configurable thread count (--threads, default 4) - Support downloading all URLs listed in a text file (one URL per line) via --list flag - Verify downloaded file integrity via --checksum flag accepting algorithm:hash format (e.g., --checksum sha256:abc123...) - Support custom HTTP headers via --header flag (e.g., --header "Authorization: Bearer token") - Support following HTTP redirects (up to 10 hops) and report the final URL - Set connection timeout via --timeout flag (default 30 seconds) and retry failed downloads via --retries flag (default 3) with exponential backoff - Save files to a directory specified by --output flag (default: current directory), using the filename from the URL or Content-Disposition header - Print a download summary to console: file name, size, time taken, average speed, and checksum verification result - If no URLs are given, download a set of sample public domain text files from Project Gutenberg, display progress for each, and print a summary table - Handle errors: DNS resolution failures, SSL certificate errors, HTTP 4xx/5xx responses, disk full, and network timeouts Create a complete JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include: - Source code - package.json with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
downloader.js
/**
* HTTP File Downloader with progress bars, resumable downloads,
* concurrent transfers, and checksum verification.
*
* Dependencies: axios (1.6.7), cli-progress (3.12.0)
*/
const axios = require('axios');
const cliProgress = require('cli-progress');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { pipeline } = require('stream/promises');
const { Transform } = require('stream');
/**
* Represents a download task with status tracking.
*/
class DownloadTask {
constructor(url, outputPath) {
this.url = url;
this.outputPath = outputPath;
this.expectedChecksum = null;
this.checksumAlgorithm = 'sha256';
this.downloadedBytes = 0;
this.totalBytes = 0;
this.status = 'pending';
this.error = null;
}
}
/**
* HTTP File Downloader with advanced features.
*/
class FileDownloader {
static DEFAULT_CHUNK_SIZE = 65536;
static DEFAULT_TIMEOUT = 30000;
static DEFAULT_MAX_RETRIES = 3;
static DEFAULT_MAX_WORKERS = 4;
constructor(options = {}) {
this.outputDir = options.outputDir || '.';
this.timeout = options.timeout || FileDownloader.DEFAULT_TIMEOUT;
this.maxRetries = options.maxRetries || FileDownloader.DEFAULT_MAX_RETRIES;
this.maxWorkers = options.maxWorkers || FileDownloader.DEFAULT_MAX_WORKERS;
this.headers = options.headers || {};
this.verifySsl = options.verifySsl !== false;
// Ensure output directory exists
if (!fs.existsSync(this.outputDir)) {
fs.mkdirSync(this.outputDir, { recursive: true });
}
// Create multi-bar container
this.multiBar = new cliProgress.MultiBar({
clearOnComplete: false,
hideCursor: true,
format: '{filename} |{bar}| {percentage}% | {downloaded}/{total} | {speed}',
}, cliProgress.Presets.shades_grey);
}
/**
* Get remote file size via HEAD request.
*/
async getFileSize(url) {
try {
const response = await axios.head(url, {
timeout: this.timeout,
headers: this.headers,
maxRedirects: 5,
});
const contentLength = response.headers['content-length'];
return contentLength ? parseInt(contentLength, 10) : null;
} catch {
return null;
}
}
/**
* Check if server supports range requests.
*/
async supportsRange(url) {
try {
const response = await axios.head(url, {
timeout: this.timeout,
headers: this.headers,
});
const acceptRanges = response.headers['accept-ranges'];
return acceptRanges && acceptRanges.toLowerCase() !== 'none';
} catch {
return false;
}
}
/**
* Compute file checksum.
*/
computeChecksum(filepath, algorithm = 'sha256') {
return new Promise((resolve, reject) => {
const hash = crypto.createHash(algorithm);
const stream = fs.createReadStream(filepath);
stream.on('data', (chunk) => hash.update(chunk));
stream.on('end', () => resolve(hash.digest('hex')));
stream.on('error', reject);
});
}
/**
* Format bytes to human-readable string.
*/
formatBytes(bytes) {
if (bytes === 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
const k = 1024;
const i = Math.floor(Math.log(bytes) / Math.log(k));
return (bytes / Math.pow(k, i)).toFixed(1) + ' ' + units[i];
}
/**
* Get output file path from URL.
*/
getOutputPath(url, outputPath) {
if (outputPath) {
return path.isAbsolute(outputPath)
? outputPath
: path.join(this.outputDir, outputPath);
}
const urlPath = new URL(url).pathname;
let filename = path.basename(urlPath) || 'downloaded_file';
return path.join(this.outputDir, filename);
}
/**
* Download a single file with progress bar and resume support.
*/
async downloadFile(url, options = {}) {
const {
outputPath = null,
expectedChecksum = null,
checksumAlgorithm = 'sha256',
resume = true,
showProgress = true,
} = options;
const task = new DownloadTask(url, this.getOutputPath(url, outputPath));
task.expectedChecksum = expectedChecksum;
task.checksumAlgorithm = checksumAlgorithm;
let attempt = 0;
while (attempt < this.maxRetries) {
attempt++;
try {
// Get file size
const totalSize = await this.getFileSize(url);
task.totalBytes = totalSize || 0;
// Handle resume
let initialPos = 0;
const requestHeaders = { ...this.headers };
let flags = 'w';
if (resume && fs.existsSync(task.outputPath)) {
const stats = fs.statSync(task.outputPath);
const existingSize = stats.size;
if (totalSize && existingSize === totalSize) {
task.downloadedBytes = existingSize;
task.status = 'completed';
if (expectedChecksum) {
const actual = await this.computeChecksum(task.outputPath, checksumAlgorithm);
if (actual.toLowerCase() !== expectedChecksum.toLowerCase()) {
task.status = 'checksum_failed';
task.error = 'Checksum verification failed';
}
}
return task;
}
if (totalSize && existingSize < totalSize && await this.supportsRange(url)) {
initialPos = existingSize;
requestHeaders['Range'] = `bytes=${existingSize}-`;
flags = 'a';
task.downloadedBytes = existingSize;
}
}
// Perform download
const response = await axios({
method: 'get',
url,
responseType: 'stream',
timeout: this.timeout,
headers: requestHeaders,
maxRedirects: 5,
});
if (!task.totalBytes) {
const cl = response.headers['content-length'];
task.totalBytes = cl ? parseInt(cl, 10) + initialPos : 0;
}
task.status = 'downloading';
// Ensure output directory exists
const dir = path.dirname(task.outputPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Create progress bar
let progressBar = null;
if (showProgress) {
const filename = path.basename(task.outputPath);
const truncName = filename.length > 25
? filename.substring(0, 22) + '...'
: filename.padEnd(25);
progressBar = this.multiBar.create(task.totalBytes || 100, initialPos, {
filename: truncName,
downloaded: this.formatBytes(task.downloadedBytes),
total: this.formatBytes(task.totalBytes),
speed: 'N/A',
});
}
const startTime = Date.now();
// Create progress tracking transform stream
const progressStream = new Transform({
transform: (chunk, encoding, callback) => {
task.downloadedBytes += chunk.length;
if (progressBar) {
const elapsed = (Date.now() - startTime) / 1000;
const speed = elapsed > 0
? this.formatBytes((task.downloadedBytes - initialPos) / elapsed) + '/s'
: 'N/A';
progressBar.update(task.downloadedBytes, {
downloaded: this.formatBytes(task.downloadedBytes),
total: this.formatBytes(task.totalBytes),
speed,
});
}
callback(null, chunk);
},
});
const writeStream = fs.createWriteStream(task.outputPath, { flags });
await pipeline(response.data, progressStream, writeStream);
if (progressBar) {
progressBar.stop();
}
task.status = 'completed';
// Verify checksum
if (expectedChecksum) {
const actual = await this.computeChecksum(task.outputPath, checksumAlgorithm);
if (actual.toLowerCase() !== expectedChecksum.toLowerCase()) {
task.status = 'checksum_failed';
task.error = `Checksum mismatch (expected: ${expectedChecksum}, got: ${actual})`;
}
}
return task;
} catch (err) {
task.error = err.message;
if (attempt >= this.maxRetries) {
task.status = 'failed';
return task;
}
// Exponential backoff
await new Promise(resolve => setTimeout(resolve, 500 * Math.pow(2, attempt - 1)));
}
}
return task;
}
/**
* Download multiple files concurrently.
*/
async downloadMultiple(urls, options = {}) {
const { resume = true, showProgress = true } = options;
const results = [];
// Process in batches based on maxWorkers
for (let i = 0; i < urls.length; i += this.maxWorkers) {
const batch = urls.slice(i, i + this.maxWorkers);
const batchResults = await Promise.allSettled(
batch.map(url =>
this.downloadFile(url, { resume, showProgress })
)
);
for (const result of batchResults) {
if (result.status === 'fulfilled') {
results.push(result.value);
} else {
const failed = new DownloadTask(url, 'unknown');
failed.status = 'failed';
failed.error = result.reason?.message || 'Unknown error';
results.push(failed);
}
}
}
return results;
}
/**
* Download files with checksum verification.
*/
async downloadWithChecksums(downloads, options = {}) {
const { resume = true, showProgress = true } = options;
const results = [];
for (let i = 0; i < downloads.length; i += this.maxWorkers) {
const batch = downloads.slice(i, i + this.maxWorkers);
const batchResults = await Promise.allSettled(
batch.map(dl =>
this.downloadFile(dl.url, {
outputPath: dl.outputPath,
expectedChecksum: dl.checksum,
checksumAlgorithm: dl.algorithm || 'sha256',
resume,
showProgress,
})
)
);
for (const result of batchResults) {
if (result.status === 'fulfilled') {
results.push(result.value);
} else {
const failed = new DownloadTask('unknown', 'unknown');
failed.status = 'failed';
failed.error = result.reason?.message || 'Unknown error';
results.push(failed);
}
}
}
return results;
}
/**
* Stop the multi-bar and clean up.
*/
close() {
this.multiBar.stop();
}
}
/**
* Parse command-line arguments.
*/
function parseArgs() {
const args = process.argv.slice(2);
const options = {
urls: [],
outputDir: '.',
workers: 4,
noResume: false,
noProgress: false,
checksum: null,
algorithm: 'sha256',
timeout: 30000,
retries: 3,
jsonFile: null,
};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '-o':
case '--output-dir':
options.outputDir = args[++i];
break;
case '-w':
case '--workers':
options.workers = parseInt(args[++i], 10);
break;
case '--no-resume':
options.noResume = true;
break;
case '--no-progress':
options.noProgress = true;
break;
case '--checksum':
options.checksum = args[++i];
break;
case '--algorithm':
options.algorithm = args[++i];
break;
case '--timeout':
options.timeout = parseInt(args[++i], 10) * 1000;
break;
case '--retries':
options.retries = parseInt(args[++i], 10);
break;
case '--json':
options.jsonFile = args[++i];
break;
case '-h':
case '--help':
console.log('Usage: node downloader.js [options] <url1> [url2] ...');
console.log('Options:');
console.log(' -o, --output-dir <dir> Output directory (default: current)');
console.log(' -w, --workers <num> Concurrent downloads (default: 4)');
console.log(' --no-resume Disable resumable downloads');
console.log(' --no-progress Disable progress bars');
console.log(' --checksum <hash> Expected checksum (single URL)');
console.log(' --algorithm <alg> Checksum algorithm (default: sha256)');
console.log(' --timeout <secs> Request timeout in seconds');
console.log(' --retries <num> Maximum retries (default: 3)');
console.log(' --json <file> Download from JSON config file');
process.exit(0);
break;
default:
if (!args[i].startsWith('-')) {
options.urls.push(args[i]);
}
break;
}
}
return options;
}
async function main() {
const args = parseArgs();
if (args.urls.length === 0 && !args.jsonFile) {
console.error('Error: No URLs provided. Use --help for usage information.');
process.exit(1);
}
const downloader = new FileDownloader({
outputDir: args.outputDir,
timeout: args.timeout,
maxRetries: args.retries,
maxWorkers: args.workers,
});
let tasks;
try {
if (args.jsonFile) {
const jsonContent = fs.readFileSync(args.jsonFile, 'utf-8');
const downloads = JSON.parse(jsonContent);
tasks = await downloader.downloadWithChecksums(downloads, {
resume: !args.noResume,
showProgress: !args.noProgress,
});
} else if (args.urls.length === 1) {
const task = await downloader.downloadFile(args.urls[0], {
expectedChecksum: args.checksum,
checksumAlgorithm: args.algorithm,
resume: !args.noResume,
showProgress: !args.noProgress,
});
tasks = [task];
} else {
tasks = await downloader.downloadMultiple(args.urls, {
resume: !args.noResume,
showProgress: !args.noProgress,
});
}
} finally {
downloader.close();
}
// Print summary
console.log('\n--- Download Summary ---');
let failedCount = 0;
for (const task of tasks) {
const icon = task.status === 'completed' ? 'OK' : 'FAIL';
console.log(`[${icon}] ${task.url}`);
console.log(` -> ${task.outputPath}`);
console.log(` Status: ${task.status}`);
if (task.error) {
console.log(` Error: ${task.error}`);
}
console.log(` Size: ${task.downloadedBytes.toLocaleString()} bytes`);
if (task.status !== 'completed') failedCount++;
}
if (failedCount > 0) {
console.log(`\n${failedCount} download(s) failed.`);
process.exit(1);
} else {
console.log(`\nAll ${tasks.length} download(s) completed successfully.`);
}
}
// Export for use as module
module.exports = { FileDownloader, DownloadTask };
// Run if executed directly
if (require.main === module) {
main().catch(err => {
console.error('Fatal error:', err.message);
process.exit(1);
});
}
package.json
{
"name": "http-file-downloader",
"version": "1.0.0",
"description": "HTTP file downloader with progress bars, resumable downloads, concurrent transfers, and checksum verification.",
"main": "downloader.js",
"scripts": {
"start": "node downloader.js",
"download": "node downloader.js"
},
"keywords": [
"download",
"http",
"progress",
"resume",
"checksum",
"concurrent"
],
"license": "MIT",
"dependencies": {
"axios": "1.6.7",
"cli-progress": "3.12.0"
}
}
README.md
# HTTP File Downloader (JavaScript - Trial 1)
An HTTP file downloader featuring progress bars, resumable downloads, concurrent transfers, and checksum verification.
## Dependencies
- **axios** (1.6.7) - Promise-based HTTP client for download requests
- **cli-progress** (3.12.0) - Multi-bar progress display in the terminal
## Setup
```bash
npm install
```
## Usage
### Download a single file
```bash
node downloader.js https://example.com/file.zip
```
### Download multiple files concurrently
```bash
node downloader.js https://example.com/file1.zip https://example.com/file2.zip -w 4
```
### Download with checksum verification
```bash
node downloader.js https://example.com/file.zip --checksum abc123 --algorithm sha256
```
### Specify output directory
```bash
node downloader.js https://example.com/file.zip -o ./downloads
```
### Download from JSON config
```bash
node downloader.js --json config.json
```
## Features
- **Progress Bars**: Multi-bar terminal display showing percentage, size, and speed via cli-progress
- **Resumable Downloads**: Automatically resumes interrupted downloads using HTTP Range headers
- **Concurrent Transfers**: Batch-based parallel downloads with configurable concurrency
- **Checksum Verification**: Verify downloads against MD5, SHA1, SHA256, or SHA512 checksums
- **Retry Logic**: Automatic retry with exponential backoff on failures
- **JSON Config**: Load download tasks from JSON configuration files
## API Usage
```javascript
const { FileDownloader } = require('./downloader');
const downloader = new FileDownloader({
outputDir: './downloads',
maxWorkers: 4,
});
// Single download
const task = await downloader.downloadFile('https://example.com/file.zip');
// Multiple concurrent downloads
const tasks = await downloader.downloadMultiple([
'https://example.com/file1.zip',
'https://example.com/file2.zip',
]);
downloader.close();
```