How to Use IronZIP with GitHub Copilot

Curtis Chau
Curtis Chau
Updated: October 19, 2026

Some .NET systems write logs into one folder per service, and their operations team wants each day of them as one .tar.gz to copy to a Linux host and unpack with tar -xzf. The job that builds those bundles belongs to the same Visual Studio solution as the services.

Copilot CLI can build that job in a terminal, Copilot Chat and agent mode can build it inside Visual Studio or VS Code, and the cloud agent can turn a GitHub issue about it into a pull request. IronZIP writes and unpacks the TAR and the GZIP from .NET, and two instruction files in the repository tell every Copilot surface in this guide how its TAR calls behave.

The create TAR example, the create GZIP example and the extract GZIP example show the calls this guide builds on.

Quickstart
NuGetInstall with NuGet

PM > Install-Package IronZip

Install IronZIP by running the command above in the NuGet Package Manager Console, or search for the package in the NuGet Package Manager.

Start copilot at the solution root and send this prompt. Copilot Chat in Visual Studio names files with # where the CLI uses @.

Read @docs/bundle-format.md, then fill in @src/LogArchiver/Archiving/LogBundle.cs.

Add one public static method that works like this:
- Its signature is Create(string logsRoot, DateOnly day, string outputFolder).
- It writes the day's bundle and returns its path.
- It returns null when the day has no logs.
- It builds the TAR and the GZIP with IronZIP.
- IronZIP comes from the IronZip package and lives in the IronZip namespace.

Change the bundle command in Program.cs to call Create.
It prints the bundle path, or the message the format document gives for a day without logs.
Run it for 2026-10-10, then list the bundle with tar -tvzf and show me the listing.
Text

The path-specific instruction file states IronZIP's TAR naming rule, so the prompt leaves it out.

Store the key in the IRONZIP_LICENSE_KEY environment variable or a repository secret, out of prompts and commits. In a probe that set only that variable, License.IsLicensed returned false, so Program.cs copies the value into IronZip.License.LicenseKey. To set the key in a configuration file instead, follow the license key guide. Leave branch protection and a human approval on for the cloud agent's pull requests.



How do you set IronZIP conventions for Copilot?

GitHub's support reference lists .github/copilot-instructions.md for Copilot CLI, for the cloud agent and for Copilot Chat in both Visual Studio and VS Code, so the package, license and test rules go there. Visual Studio ignores the file until the custom instructions checkbox in its Copilot Chat options is selected.

Archive code in this repo follows these rules:

- Archive code uses IronZIP from the IronZip NuGet package, namespace IronZip.
- Add no other compression package.
- Keep System.Formats.Tar and System.IO.Compression out of src/.
- Tests may use them to read bundles.
- Change the IronZip version in Directory.Packages.props only for an upgrade task.
- IRONZIP_LICENSE_KEY holds the license key.
- Never put it in code, settings, tests or output, and never echo it.
- Check only that it is set.
- docs/bundle-format.md is the contract for every bundle.
- Do not edit it, and report any place where the code departs from it.
- IronZIP documentation for agents: https://ironsoftware.com/zip/csharp/llms.txt
- Find each IronZIP member in IronZip.xml in the NuGet cache before using it.
- If a member is missing there, say so.

The tests follow these rules:

- A test that calls IronZIP copies IRONZIP_LICENSE_KEY into License.LicenseKey first.
- IronZIP never reads that variable on its own.
- Write bundles into a temp folder.
- Unpack them into a second empty folder and compare files by SHA-256.
- Never compare archive bytes.
Markdown

How do you give the archive code its own rules?

Save the archive rules as .github/instructions/archives.instructions.md. GitHub documents that Copilot CLI includes a path-specific file only when its applyTo value matches a file the CLI is working with. The /instructions command lists the instruction files the CLI found and turns each one on or off.

---
applyTo: "src/LogArchiver/**,tests/LogArchiver.Tests/**"
---

IronZIP's TAR and GZIP calls behave this way in the log archiver and its tests:

- IronTarArchive.Add keeps only the file name and drops every folder.
- Two files with one name become two entries with that name, and unpacking keeps the last.
- Copy each file into a staging folder under its final entry name and add the copy.
- IronTarArchive.Add on a folder adds nothing and throws nothing.
- A GZIP file holds one stream.
- Save the TAR first, then add that single file to an IronGZipArchive.
- Two files added to one IronGZipArchive come out as one file with both contents joined.
- GetArchiveEntryNames throws on a new archive that was never saved.
- Call it on an archive opened from a file.
- No entry can be added after SaveAs. Start a new archive instead.
- Unpack a .tar.gz with IronGZipArchive.ExtractTGZArchiveToDirectory.
- Always unpack into a new empty folder.
- Unpacking replaces files of the same name and leaves every other file where it was.
- IronTarArchive.ExtractArchiveToDirectory writes an entry named ../x or /x outside the target.
- Check every name from GetArchiveEntryNames first.
- Without a key, IronTarArchive.Add throws LicensingException.
- SaveAs then still writes an empty archive.
- Never catch that exception and continue.
Markdown

What does IronZIP's TAR writer keep from a file path?

Each service here names its log after the day, so logs/api/2026-10-10.log and logs/worker/2026-10-10.log share a file name. Added straight from their folders with IronZIP 2026.10.2, the four logs for October 10 became four TAR entries named 2026-10-10.log. tar -tvzf listed all four, and nothing threw. Unpacked with IronZIP, the bundle held one 2026-10-10.log of 19,813 bytes, the worker log added last.

With each log staged as a copy named for its service, such as worker-2026-10-10.log, the bundle passed all 24 checks of a Python format checker that reads bundles without IronZIP. Every entry had mode 700, owner 0 and the time of the save rather than the file's own timestamp. Two bundles of the same logs built two seconds apart differed from the fifth byte, where GZIP stores its time.

How do you check a bundle after it is written?

Pointed at a used folder, ExtractTGZArchiveToDirectory replaced files of the same name and kept the rest, so a check that reuses a folder can count a stale file.

Add a verify <bundle> command to Program.cs beside bundle.

The command runs these steps in order:
1. Unpack the bundle with IronZIP into a fresh temp folder.
2. Read manifest.txt there.
3. Confirm the folder holds manifest.txt plus exactly the files it names.
4. Confirm those files have matching sizes and SHA-256 hashes.

It reports the outcome this way:
- Print one line for each problem, and exit 1 if there is one.
- If there is none, print the file count and exit 0.
- If IronZIP cannot unpack the bundle, print its exception and exit 1.
- Delete the temp folder either way.

Run verify on the 2026-10-10 bundle.
Then run it on a copy of that bundle in a temp folder with the middle byte flipped.
Show me both results.
Text

In the calibration probe, the flipped byte made IronZIP throw TarException ("Header checksum is invalid") after it had already written the first log, so a failed unpack can leave a partial folder.

How do you test an IronZIP bundle?

A test that reads each bundle with System.Formats.Tar as well as IronZIP catches a naming fault that an IronZIP unpack hides. GitHub's CLI reference says /plan starts a planning session in which Copilot CLI can explore the code but is blocked from editing project files.

/plan an xUnit suite in tests/LogArchiver.Tests for LogBundle.Create.
Each test copies the logs folder to a temp directory and writes its bundles there.

Read each bundle back in two ways:
- unpacked by IronZIP into an empty folder
- listed with System.Formats.Tar over a GZipStream, which needs no IronZIP

The suite shows each of these points:
- The 2026-10-10 bundle has one top-level entry per service log plus manifest.txt.
- No entry name is repeated.
- Each unpacked log matches its source by SHA-256.
- The rotated .log.1 file and other days are left out.
- A day without logs writes nothing.
- Only the bundle remains in the output folder.

After I approve the plan, write the tests and run them with dotnet test.
Tell me how many passed and how many failed.
Text

Why does restoring an old TAR write outside its folder?

Bundles from before 2026 are plain TAR files, and a teammate's restore command unpacks them with IronTarArchive.ExtractArchiveToDirectory. In a probe, that call wrote an entry named ../escaped-dotdot.txt into the folder above the target, and one with an absolute name to the root of the drive. ExtractTGZArchiveToDirectory refused the same ../ entry with InvalidNameException, though only after unpacking the entries before it.

A setup commit adds RestoreCommand.cs, RestoreTests.cs and two TAR files under TestData/restore.

A teammate added @src/LogArchiver/Archiving/RestoreCommand.cs.
It restores the plain .tar archives kept from before 2026.
RestoreTests now fails on TestData/restore/hostile.tar.

Before changing any code, do these three things:
1. Run that test.
2. Print the entry names IronZIP reports for both .tar files in TestData/restore.
3. Explain how a file ends up outside the target folder.

Then fix it in RestoreCommand.cs alone.
An archive with any entry that would be written outside the target must be refused.
Refuse it before a single file is written.
Leave TestData and the tests untouched.
Run dotnet test and report the result.
Text

A reference fix written for calibration read the names with GetArchiveEntryNames, refused any that resolved outside the target, and passed both restore tests.

What makes an issue the cloud agent can act on?

The cloud agent sees an assigned issue's title, description, existing comments and extra instructions. GitHub says the agent does not see comments added after the assignment, so the acceptance test goes in the issue body.

Title: Write a .tar.bz2 copy of each bundle for cold storage

This issue touches the following:
- Files: src/LogArchiver/Archiving/LogBundle.cs, tests/LogArchiver.Tests
- IronZip version: whatever Directory.Packages.props pins
- Target framework: .NET 9

After the change, the bundle command behaves like this:
- bundle <date> also writes out/cold/logs-<date>.tar.bz2.
- That file holds the same TAR as the .tar.gz.
- The pull request text proposes the matching docs/bundle-format.md rule.
- That rule goes in a separate pull request.

Today, cold storage bills by size and receives the .tar.gz only.

The work stays inside these limits:
- Build the TAR once and compress that one file twice.
- Use IronGZipArchive and IronBZip2Archive for the two copies.
- Keep the .tar.gz unchanged.
- No new packages.

The issue is done when these checks pass:
- dotnet test passes.
- A new test unpacks the .tar.bz2 with IronBZip2Archive.ExtractArchiveToDirectory.
- It unpacks into an empty folder.
- It finds every log and manifest.txt identical to the .tar.gz contents.
Markdown

IronBZip2Archive takes no compression level, and its ExtractArchiveToDirectory unpacked a .tar.bz2 straight to the files inside the TAR.

Why do the cloud agent's tests fail on an IronZIP project?

The cloud agent works on Ubuntu Linux by default. IronZIP ships a single .NET Standard 2.0 assembly, and neither its package nor IronSoftware.Common, its one dependency, includes a native binary, so a restore is all the runner needs. Without an Agents secret, IRONZIP_LICENSE_KEY is not set on the runner. With no key, IronTarArchive.Add threw LicensingException with "Production License Required", and SaveAs on the same archive still wrote a 10,240-byte TAR with no entries. Code that catches the exception and continues writes empty bundles.

Copilot starts work after the copilot-setup-steps job finishes, and GitHub triggers that job only when .github/workflows/copilot-setup-steps.yml is present on the default branch.

Write the cloud agent's setup file, .github/workflows/copilot-setup-steps.yml.

The file holds one job with these settings:
- Name the job copilot-setup-steps.
- Give it contents: read.
- Run it on workflow_dispatch plus pull requests that edit this file.

The job runs these steps in order:
1. Fetch the repository.
2. Set up the .NET 9 SDK.
3. Restore.
4. Build the solution in Release.
5. Print tar --version, since the agent can compare its bundles with GNU tar.
6. Print a warning that names IRONZIP_LICENSE_KEY when that variable is empty.

IronZIP will not add entries without the key, which is why step 6 warns.
Step 6 always exits 0.
Print the YAML.
Text

Save the key on the Agents tab of Secrets and variables, under Security in the repository's Settings, because the cloud agent cannot see Actions, Codespaces or Dependabot secrets.

What should you check before merging?

GitHub's documentation shows /review passed to copilot -p, so the review runs in its own session, apart from the coding session.

/review every commit after the first on this branch.
Compare those commits with docs/bundle-format.md and the two instruction files in .github.

Report each of these:
- an IronTarArchive.Add whose path does not already end in the final entry name
- a GetArchiveEntryNames on an archive that was never saved or opened
- an Add after SaveAs
- any unpacking into a folder that may hold files already
- an IronTarArchive.ExtractArchiveToDirectory without a check of every entry name first
- a catch that could hide a LicensingException
- an IronZIP member absent from IronZip.xml in the NuGet cache

Give the file and line of each finding, and modify nothing.
Text

What did the prompts produce in a real run?

Copilot CLI received the six prompts in order, unattended, the first five in one session and the review prompt in a separate copilot -p call. The target was a .NET 9 log solution made for this guide, with seven logs across four services, a console job that only counted them, an empty xUnit project and the format document. Every figure below except the review findings and the last bullet comes from checks run after the session ended. The acceptance prompt in the AI coding assistants guide checks ZIP and .tgz archives the same two ways, by distinct entry names and by SHA-256 after extraction.

  • The format checker passed 2 of 4 checks on the untouched job, which wrote no bundle.
  • After the first prompt, the checker passed 24 of 24 checks for October 10, 9 and 8.
  • The October 10 bundle held five entries, the four logs under service-prefixed names and manifest.txt.
  • The checker still passed 24 of 24 after the last prompt.
  • On the October 10 bundle, the verify command counted 4 logs and exited 0.
  • A copy with its middle byte inverted made the command exit 1 on the TarException.
  • Neither run of the command left a temp folder.
  • Both tests of the planned suite passed.
  • One of those tests failed on a copy of the final code that added the logs straight from their service folders.
  • The setup commit left the restore tests at 1 passed and 1 failed, and hostile.tar threw no exception.
  • The restore prompt changed RestoreCommand.cs and no other file.
  • Both restore tests passed after that prompt.
  • The setup workflow parsed as one copilot-setup-steps job of 6 steps, with the key check last.
  • The final Release build finished with 0 warnings and 0 errors.
  • The review left every file unchanged and reported three findings, one of them a restore into a folder that the code never checked was empty.
  • Some steps ran differently from the page. The plan prompt ran in plan mode with autopilot, so Copilot CLI approved its own plan. Its tests read entry names with System.Formats.Tar and compared hashes on the files IronZIP unpacked. Copilot CLI's own corrupt copy had one bit flipped, unpacked without an exception and failed on three SHA-256 mismatches. The issue, the cloud agent, the setup workflow on Actions and the Agents secret need a GitHub repository, so none of them ran.

The Cursor guide ports ZIP bundle code to IronZIP, where Add also drops the folder from each entry name, and its ported bundle held 7 entries, each under a unique flat name. The Google Antigravity guide also checks each archive by extracting it with IronZIP and comparing every file by SHA-256. The shared run record lists Copilot CLI's version, the model, the run time and the test machine for this guide.


Troubleshooting


Questions?

If you have any questions, reach out to support@ironsoftware.com

Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.

Read More

Ready to Get Started?

Nuget Downloads 24,698Version:2026.10just released