HTTP File Downloader (javascript, written by Codex)
envgap__codex__javascript-t1-24
Written by a coding agent; not on GitHubWritten 2026-03-03
01 / FAILURE SIGNATURE
As the study recorded it
None
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
codex/javascript-t1 #24 · read the task the agent was given
Codex 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
4 files, exactly as written, before any repair.
package-lock.json
{
"name": "http-file-downloader",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "http-file-downloader",
"version": "1.0.0",
"dependencies": {
"got": "14.4.3",
"p-limit": "5.0.0"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/got": {
"version": "14.4.3"
},
"node_modules/p-limit": {
"version": "5.0.0"
}
}
}
package.json
{
"name": "http-file-downloader",
"version": "1.0.0",
"private": true,
"type": "module",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js"
},
"engines": {
"node": ">=20.0.0"
},
"dependencies": {
"got": "14.4.3",
"p-limit": "5.0.0"
}
}
README.md
# HTTP File Downloader (JavaScript) Downloads files from HTTP/HTTPS with progress display, resumable downloads, retry/backoff, redirect handling, parallel mode, checksum verification, custom headers, list-file input, and batch summary output. ## Requirements - Ubuntu 22.04 - Node.js 20+ (LTS) ## Dependencies (Pinned) - `got@14.4.3` - `p-limit@5.0.0` ## Setup ```bash npm install ``` ## Run ```bash node src/index.js https://example.com/file.txt node src/index.js https://example.com/file.bin --resume --output ./downloads node src/index.js --list urls.txt --parallel --threads 4 --timeout 30 --retries 3 node src/index.js https://example.com/file --checksum sha256:abc123... node src/index.js https://example.com/file --header "Authorization: Bearer TOKEN" node src/index.js ``` ## Notes - `--parallel` enables concurrent downloads, bounded by `--threads` (default `4`). - `--resume` uses HTTP `Range` where supported. - `--checksum` applies verification to the first item in a multi-download run. - No-input mode downloads sample Project Gutenberg text files.
src/index.js
import fs from "fs";
import path from "path";
import crypto from "crypto";
import { URL } from "url";
import got from "got";
import pLimit from "p-limit";
const SAMPLE_URLS = [
"https://www.gutenberg.org/files/11/11-0.txt",
"https://www.gutenberg.org/files/1342/1342-0.txt",
"https://www.gutenberg.org/files/1661/1661-0.txt",
];
function parseArgs(argv) {
const options = {};
const positional = [];
for (let i = 0; i < argv.length; i += 1) {
const t = argv[i];
if (t.startsWith("--")) {
const key = t.slice(2);
const next = argv[i + 1];
const value = next && !next.startsWith("--") ? next : true;
if (value !== true) i += 1;
if (key === "header") {
if (!options.header) options.header = [];
options.header.push(String(value));
} else options[key] = value;
} else positional.push(t);
}
return { options, positional };
}
function parseHeaders(values = []) {
const out = {};
for (const h of values) {
const idx = h.indexOf(":");
if (idx <= 0) continue;
const k = h.slice(0, idx).trim();
const v = h.slice(idx + 1).trim();
if (k) out[k] = v;
}
return out;
}
function parseChecksum(raw) {
if (!raw) return null;
const idx = raw.indexOf(":");
if (idx <= 0) throw new Error("Invalid --checksum. Use algorithm:hash");
const algorithm = raw.slice(0, idx).toLowerCase();
const expected = raw.slice(idx + 1).toLowerCase();
return { algorithm, expected };
}
function fromContentDisposition(v) {
if (!v) return null;
const m = /filename\*?=(?:UTF-8'')?"?([^\";]+)"?/i.exec(v);
return m ? decodeURIComponent(m[1]) : null;
}
function filenameFromUrl(url) {
try {
const u = new URL(url);
const base = path.basename(u.pathname || "");
return base && base !== "/" ? base : "download.bin";
} catch {
return "download.bin";
}
}
function formatBytes(n) {
if (!Number.isFinite(n) || n < 0) return "?";
const units = ["B", "KB", "MB", "GB", "TB"];
let v = n;
let i = 0;
while (v >= 1024 && i < units.length - 1) {
v /= 1024;
i += 1;
}
return `${v.toFixed(i === 0 ? 0 : 2)} ${units[i]}`;
}
function formatEta(sec) {
if (!Number.isFinite(sec) || sec < 0) return "--:--";
const s = Math.floor(sec % 60);
const m = Math.floor((sec / 60) % 60);
const h = Math.floor(sec / 3600);
if (h > 0) return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
}
function checksumFile(filePath, algorithm) {
return new Promise((resolve, reject) => {
const hash = crypto.createHash(algorithm);
const stream = fs.createReadStream(filePath);
stream.on("error", reject);
stream.on("data", (chunk) => hash.update(chunk));
stream.on("end", () => resolve(hash.digest("hex")));
});
}
async function downloadOne(url, opts, checksumCfg, idx, totalCount) {
const timeoutMs = Number(opts.timeout ?? 30) * 1000;
const retries = Number(opts.retries ?? 3);
const outputDir = path.resolve(String(opts.output ?? "."));
fs.mkdirSync(outputDir, { recursive: true });
const baseName = filenameFromUrl(url);
const guessPath = path.join(outputDir, baseName);
const existing = opts.resume && fs.existsSync(guessPath) ? fs.statSync(guessPath).size : 0;
const headers = { ...parseHeaders(opts.header), ...(existing > 0 ? { Range: `bytes=${existing}-` } : {}) };
let attempt = 0;
while (attempt <= retries) {
const startedAt = Date.now();
let downloaded = 0;
let total = 0;
let finalUrl = url;
let outPath = guessPath;
let writer;
let statusCode = 0;
try {
await new Promise((resolve, reject) => {
const req = got.stream(url, {
method: "GET",
headers,
maxRedirects: 10,
timeout: { request: timeoutMs },
throwHttpErrors: false,
retry: { limit: 0 },
https: { rejectUnauthorized: true },
});
req.on("response", (res) => {
statusCode = res.statusCode ?? 0;
if (statusCode >= 400) {
req.destroy(new Error(`HTTP ${statusCode}`));
return;
}
finalUrl = res.url || url;
const cd = res.headers["content-disposition"];
const name = fromContentDisposition(Array.isArray(cd) ? cd[0] : cd) || filenameFromUrl(finalUrl);
outPath = path.join(outputDir, name);
const isResume = opts.resume && existing > 0 && statusCode === 206 && outPath === guessPath;
const mode = isResume ? "a" : "w";
writer = fs.createWriteStream(outPath, { flags: mode });
const len = Number(res.headers["content-length"] || 0);
total = len > 0 ? len + (isResume ? existing : 0) : 0;
downloaded = isResume ? existing : 0;
});
req.on("data", (chunk) => {
downloaded += chunk.length;
if (writer) writer.write(chunk);
const elapsed = Math.max(0.001, (Date.now() - startedAt) / 1000);
const speed = (downloaded - (existing > 0 ? existing : 0)) / elapsed;
const pct = total > 0 ? (downloaded * 100) / total : 0;
const eta = speed > 0 && total > 0 ? (total - downloaded) / speed : Number.POSITIVE_INFINITY;
process.stdout.write(`\r[${idx + 1}/${totalCount}] ${path.basename(outPath)} | ${pct.toFixed(1)}% | ${formatBytes(downloaded)}/${total > 0 ? formatBytes(total) : "?"} | ${formatBytes(speed)}/s | ETA ${formatEta(eta)} `);
});
req.on("error", (err) => {
if (writer) writer.end();
reject(err);
});
req.on("end", () => {
if (writer) writer.end();
resolve();
});
});
process.stdout.write("\n");
let checksumResult = "not requested";
if (checksumCfg) {
const gotHash = await checksumFile(outPath, checksumCfg.algorithm);
checksumResult = gotHash.toLowerCase() === checksumCfg.expected ? "ok" : `mismatch (${gotHash})`;
}
const size = fs.statSync(outPath).size;
const sec = (Date.now() - startedAt) / 1000;
return {
url,
finalUrl,
file: path.basename(outPath),
path: outPath,
size,
timeSec: sec,
avgSpeed: size / Math.max(sec, 0.001),
checksum: checksumResult,
statusCode,
};
} catch (err) {
attempt += 1;
if (attempt > retries) throw err;
const waitMs = (2 ** (attempt - 1)) * 1000;
await new Promise((r) => setTimeout(r, waitMs));
}
}
throw new Error("Download failed after retries.");
}
function printSummary(rows) {
console.log("\nDownload Summary");
console.log("File | Size | Time | Avg Speed | Checksum | Final URL");
console.log("---- | ---- | ---- | --------- | -------- | ---------");
for (const r of rows) {
console.log(`${r.file} | ${formatBytes(r.size)} | ${r.timeSec.toFixed(2)}s | ${formatBytes(r.avgSpeed)}/s | ${r.checksum} | ${r.finalUrl}`);
}
}
async function main() {
const { options, positional } = parseArgs(process.argv.slice(2));
const listUrls = options.list
? fs.readFileSync(path.resolve(String(options.list)), "utf8").split(/\r?\n/).map((s) => s.trim()).filter(Boolean)
: [];
const urls = [...positional, ...listUrls];
if (urls.length === 0) urls.push(...SAMPLE_URLS);
const checksum = parseChecksum(options.checksum ? String(options.checksum) : "");
const useParallel = Boolean(options.parallel);
const threads = Math.max(1, Number(options.threads ?? 4));
const tasks = urls.map((u, i) => async () => downloadOne(u, options, checksum && i === 0 ? checksum : null, i, urls.length));
const results = [];
if (useParallel && tasks.length > 1) {
const limit = pLimit(threads);
const settled = await Promise.all(tasks.map((t) => limit(async () => {
try {
const r = await t();
results.push(r);
} catch (e) {
results.push({
url: "unknown",
finalUrl: "n/a",
file: "n/a",
size: 0,
timeSec: 0,
avgSpeed: 0,
checksum: `failed: ${e instanceof Error ? e.message : String(e)}`,
});
}
})));
void settled;
} else {
for (const t of tasks) results.push(await t());
}
printSummary(results);
}
main().catch((e) => {
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
process.exit(1);
});