# How to Use IronQR with GitHub Copilot
Invoices, account statements, and signed contracts generated by a .NET document pipeline often include a QR code for recipients to verify document authenticity. Teams usually maintain this pipeline within a Visual Studio solution, alongside PDF-generating services, and need an assistant that works inside that solution.
GitHub Copilot operates in Visual Studio, VS Code, the terminal as Copilot CLI, and on GitHub. IronQR adds QR codes to existing PDF pages using `QrCode.StampToExistingPdfPage`. The repository's instruction files define how each Copilot interface should handle this call.
The [stamp to PDF guide](https://ironsoftware.com/qr/csharp/how-to/stamp-qr-code-to-pdf/) documents the stamping call in detail.
*as-heading:2(Quickstart)*
!!!--LIBRARY_NUGET_INSTALL_BLOCK--!!!
Start Copilot CLI in a terminal at the solution root and send the prompt. In Visual Studio chat, reference the two files with `#` and the file name instead of `@`.
```text
Open @src/DocStamp/Stamping/VerificationStamper.cs and add a public static
method StampFirstPage(string pdfPath, string documentId). Use IronQR, which
installs as the IronQR package and lives in the IronQr namespace, to draw a QR
code for https://verify.example.com/d/{documentId} inside the Scan to verify
box that @docs/stamp-layout.md specifies. That document measures from the
bottom-left corner, while IronQR takes x and y in points from the top-left and
numbers pages from 1, so convert before you call it. Stamp the path you are
given and leave copying to the caller. The license key comes from
IRONQR_LICENSE_KEY. Build the solution and fix anything that fails.
```
The prompt leaves out the stamp size on purpose. The path-specific instructions file below carries that rule, and in the run Copilot read it before writing the method.
Set `IRONQR_LICENSE_KEY` in the environment Copilot starts from, and see the [license key guide](https://ironsoftware.com/qr/csharp/get-started/license-keys/) for other places to keep the key.
<div class="hsg-featured-snippet">
<h3>Minimal Workflow (5 steps)</h3>
<ol>
<li><a class="js-modal-open" data-modal-id="trial-license-after-download" href="https://www.nuget.org/packages/IronQR/">Download the C# QR code library from NuGet</a></li>
<li>Commit the stamping rules to <code>.github/copilot-instructions.md</code> and a path-specific instructions file</li>
<li>Create the code with <code>QrWriter.Write</code>, size it with <code>Save</code>, and stamp it with <code>StampToExistingPdfPage</code></li>
<li>Prove each stamp by rendering the page to an image and decoding it with IronQR</li>
<li>Give the cloud agent an issue for the next change and check its pull request against the <a href="https://ironsoftware.com/qr/csharp/object-reference/api/">API reference</a></li>
</ol>
</div>
<br class="clear" />
---
## Where does Copilot fit into a project already in Visual Studio?
Use agent mode in Visual Studio or VS Code for a stamping change you want to watch, since it can build the solution and run the stamping job itself. Copilot CLI works the same way from a terminal. The cloud agent on GitHub takes an assigned issue and returns a pull request.
## How do you set IronQR conventions for Copilot?
Commit the IronQR conventions as `.github/copilot-instructions.md`. GitHub's support table lists the file for Copilot CLI, the cloud agent, Copilot code review and Copilot Chat in Visual Studio, VS Code and JetBrains IDEs, so each interface starts from the same rules.
```markdown
# Copilot instructions
## Verification stamps
- QR codes come from IronQR. The NuGet package is IronQR (IronQR.Linux on
Linux), the namespace is IronQr, and its exceptions live in
IronQr.Exceptions. Add no QR or barcode package from another vendor.
- The IronQR version is pinned in Directory.Packages.props. Change it only
when the task is an upgrade.
- Read the license key from the IRONQR_LICENSE_KEY environment variable. Never
write a key or a PDF password into code, configuration, tests or output.
- A stamp encodes https://verify.example.com/d/{documentId}, where documentId
is the PDF file name without its extension.
- IronQR cannot open a PDF for reading. Code that checks a stamp renders the
page with PDFtoImage and decodes the image with QrReader.
- Documentation: https://ironsoftware.com/qr/csharp/llms.txt
- When you are not sure an IronQR member exists, say so and check the
documentation before you use it.
## Tests
- Stamp copies in a temporary folder. Never stamp a file under TestData.
- Assert on the decoded value and on where the code sits on the page, never
on the bytes of a stamped PDF.
```
## How do you give the stamping code its own rules?
Rules that matter only where the stamping code lives go in `.github/instructions/stamping.instructions.md`. Its `applyTo` path pattern keeps stamp geometry out of requests about the job runner or the tests, and Copilot CLI includes the file only when it works on a matching file.
```markdown
---
applyTo: "src/DocStamp/Stamping/**"
---
- StampToExistingPdfPage and StampToExistingPdfPages overwrite the file at the
path given. The caller passes a copy.
- x and y are PDF points measured from the top-left corner of the page.
Page numbers start at 1. Page 0 throws ArgumentOutOfRangeException.
- The stamp is drawn one point per pixel of the QR image. Call
qr.Save(new QrStyleOptions(110, 0)) before stamping, or the code comes out
250 points square.
- Protected PDFs open only with the owner password, read from
DOCSTAMP_PDF_OWNER_PASSWORD. The stamped file is saved without encryption.
```
## How do you place and size a QR code on a PDF page?
The PDF format stores positions from the bottom-left corner, while IronQR counts from the top-left. With IronQR 2026.10.1, x 455 and y 30 put the code at the top-right of an A4 page, and y 702, the bottom-left figure for the same box, put it near the bottom edge.
No stamp method takes a size. In the 110-point box near the right edge of an A4 page, a code saved without `QrStyleOptions` ran off the page, and the rendered page returned no code at all. The default margin of 10 is added on every side, so a dimension of 110 with that margin gives a 130-point stamp.
Some samples pass 0 for the first page, which in 2026.10.1 throws ArgumentOutOfRangeException with the message "Page Number 0 does not exist". A page past the end fails the same way, and in each case the file was left unchanged.
```text
Statements run to three pages, and TestData/inbound/bundle-5120.pdf puts a
Letter page behind an A4 page. Add StampEveryPage(string pdfPath, string
documentId) to @src/DocStamp/Stamping/VerificationStamper.cs so every page gets
its code in its own box. StampToExistingPdfPages applies a single x and y to
each page it receives, so read the page sizes with PDFtoImage's
Conversion.GetPageSizes, group pages that share a size, and stamp once per
group. Then rework @src/DocStamp/Pipeline/StampJob.cs to copy each inbound PDF
to the outbound folder and stamp the copy. Run the job against TestData/inbound
and print the coordinates used for each page.
```
A Letter page is 612 points wide and an A4 page is 595.28, so one x value for both in a `StampToExistingPdfPages` call puts the Letter stamp 17 points to the left of its box. The misplaced stamp raises no error and still scans, which is why the tests check where it landed as well as what it says.
## How do you stamp a password-protected PDF?
The stamp methods take the password as their last argument, and IronQR's API documentation calls it "the PDF encryption or administrator password". We stamped a two-page contract encrypted with PDFsharp 6.2.4, which sets a user password for opening the file and an owner password for changing it. RC4 40-bit, RC4 128-bit and AES 128-bit copies gave the same results.
- **Owner password:** the stamp succeeded, and the saved file opened with no password.
- **User password, wrong password or none:** IronQrPdfPasswordException with "PDF could not be opened", and the file was unchanged.
- **AES 256-bit copy:** IronQrPdfPasswordException with both passwords, while PDFium opened the same file with the user password.
Two other results shape the error handling. A missing file and a text file renamed to .pdf raised the same exception. Copies of the contract encrypted by MuPDF threw PdfSharpCore.SharpZipLib.SharpZipBaseException with "Header checksum illegal", an exception from outside `IronQr.Exceptions` that stops any batch whose catch names only IronQR exceptions.
```text
Make the owner password an explicit input for contract-7310-protected.pdf. Give
@src/DocStamp/Stamping/VerificationStamper.cs an optional password parameter in
place of any environment lookup inside it, and have StampJob pass
DOCSTAMP_PDF_OWNER_PASSWORD from the environment. IronQR opens a protected file
only with its owner password and saves the stamped copy without encryption, so
add PasswordRemoved to the job's per-file summary. A document that throws
IronQrPdfPasswordException, or any other exception, moves to a quarantine folder
beside outbound with a .reason.txt naming the exception type and message, and
the batch carries on. Never print the password. Run the job and list outbound
and quarantine.
```
`PasswordRemoved` gives the next pipeline step a flag to act on, whether that step re-encrypts the file or holds it back. A stamped contract that silently loses its protection is worse than one that fails to stamp.
## How do you test that a stamped QR code scans?
The [IronQR skill file](https://ironsoftware.com/qr/csharp/skill.md) confirms that the library has no PDF loader. [PDFtoImage](https://github.com/sungaila/PDFtoImage) is an MIT-licensed package built on PDFium that renders a page at a chosen DPI, reports page sizes through `Conversion.GetPageSizes` and opens protected files with a password. A 110-point stamp decoded with `QrReader` from renders at 50, 72, 100, 150 and 300 DPI, so a failed read in these tests points at the stamp itself.
Press Shift+Tab until Copilot CLI shows plan mode, then send this prompt.
```text
Before writing anything, plan an xUnit suite for tests/DocStamp.Tests and wait
for my approval. IronQR reads images, not PDFs, so each check stamps a temp
copy, renders the page with PDFtoImage at 150 DPI, decodes the PNG with
QrReader, and scales the corner points by 72/150 to get PDF points. The suite
should prove that every page of every file in TestData/inbound decodes to its
own verification URL, that the corners sit inside the box in
docs/stamp-layout.md, that page 0 and a page past the end throw
ArgumentOutOfRangeException without touching the file, that the user password
and a wrong one both raise IronQrPdfPasswordException, and that the stamped
contract opens with no password. The owner password for the sample is in
TestData/README.md. After approval, write it and run dotnet test.
```
Plan mode suits this suite because the render-and-read helper has to exist before any single test makes sense. The last assertion pins down the behavior from the previous section. If a later IronQR release keeps the encryption, that test fails, and someone revisits `PasswordRemoved` on purpose.
## What makes an issue the cloud agent can act on?
When you assign an issue, the cloud agent receives its title, description, existing comments and any extra instructions you add. GitHub states that it does not react to comments posted after assignment, so the issue has to carry the whole brief, including how to tell the job is done.
```markdown
Title: Contracts need the verification stamp beside the signature block on the last page
**Files:** src/DocStamp/Stamping/VerificationStamper.cs, src/DocStamp/Pipeline/StampJob.cs
**IronQR version:** as pinned in Directory.Packages.props
**Framework:** .NET 9
**Expected:** Files whose names start with contract- get one stamp, on the last
page only, 110 points square, 60 points in from the left edge, with its bottom
edge 150 points above the bottom of the page. Other documents keep a stamp in
the Scan to verify box on every page.
**Actual:** Contracts are stamped on every page in the top-right box, where the
signing service prints its own seal.
**Constraints:** Keep the signature of StampEveryPage. Keep passing the owner
password from DOCSTAMP_PDF_OWNER_PASSWORD. No new packages.
**Acceptance:** dotnet test passes, plus a new test that stamps a copy of
contract-7310-protected.pdf, renders its last page, and finds exactly one code,
inside the new rectangle, that decodes to the contract's URL.
```
The legal team measured the position from the bottom of the page, so the cloud agent has to convert it with the rule in the path-specific instructions, and the acceptance test catches a missed conversion.
## Why do the cloud agent's tests fail on an IronQR project?
The cloud agent works on an Ubuntu Linux runner by default. The plain `IronQR` package depends on `IronSoftware.ReaderInternals.Windows`, and the [NuGet packages guide](https://ironsoftware.com/qr/csharp/get-started/nuget-packages/) names `IronQR.Linux` for Linux servers, containers and CI. Without a license key, `QrWriter.Write` throws LicensingException before any stamp is drawn. A red test caused by configuration looks the same as one caused by the change, so read the first exception before the diff.
A setup workflow at `.github/workflows/copilot-setup-steps.yml`, with its one job named `copilot-setup-steps`, prepares the runner before Copilot starts, and it does not trigger until the file is on the default branch.
```text
Create .github/workflows/copilot-setup-steps.yml so the cloud agent can build
and test this solution on its Ubuntu runner. GitHub ignores the file unless its
one job is named copilot-setup-steps. Trigger it from workflow_dispatch and from
pushes and pull requests that touch the file. Check out the code, install the
.NET SDK version the projects target, restore, and build. The IronQR package
brings the Windows reader, so make the projects reference IronQR.Linux in place
of IronQR when they build on Linux. Make the last step fail with a readable
message when IRONQR_LICENSE_KEY is empty, because a failed step makes Copilot
skip the steps after it. Then show the YAML and every project file change.
```
The cloud agent cannot read secrets stored for Actions, Dependabot or Codespaces. Store `IRONQR_LICENSE_KEY` and `DOCSTAMP_PDF_OWNER_PASSWORD` as Agents secrets, under Secrets and variables in the Security part of the repository's Settings, and Copilot receives both as environment variables.
## How do you diagnose a PDF that will not stamp?
Because the password failures and the unreadable files above share one exception, an assistant left to guess keeps reworking the password handling.
```text
A new e-signature vendor sent contract-7311.pdf and contract-7312.pdf, now in
TestData/vendor-batch. When StampJob runs on that folder, both end up in
quarantine with IronQrPdfPasswordException. The vendor confirms
DOCSTAMP_PDF_OWNER_PASSWORD, and contract-7310-protected.pdf still stamps with
it. Explain the cause before you change any code. First measure, reading the V,
R and Length entries of each file's /Encrypt dictionary, trying the owner and
user passwords against each, and comparing with contract-7310. Base the
explanation on those results. Then change only how StampJob words the reason
text, so an operator can see the real cause. It may read the same /Encrypt
entries, which need no password. Add no package and do not decrypt anything.
```
In the PDF standard, V 5 with R 6 marks AES 256-bit encryption, and V 2 with R 3 is RC4. Asking for those readings before the explanation gives you a claim you can check against the files. Keeping the change to the reason text keeps a second PDF library out of the project, a decision that belongs to the team that owns the contracts.
## What should you check before merging?
Send `/review` as its own `copilot -p` call. In a first run it went into the session that wrote the code, then went back to the previous task and edited `StampJob.cs`.
```text
/review the changes on this branch since its first commit. Check each IronQR
call against .github/instructions/stamping.instructions.md and against the
IronQr.xml that the IronQR.Slim package puts in the NuGet cache, and note any
place the two disagree. Confirm that pages are numbered from 1, that each code
is sized with Save(QrStyleOptions) before it is stamped, and that protected
files receive the owner password. Flag any test that stamps a file under
TestData, compares the bytes of a stamped PDF, or could write a license key or
password to output. Report the findings and change no files.
```
The XML shipped in the package describes x and y as pixels, while the instructions file says points. Both hold in practice, because the stamp is drawn one point per pixel, and a reviewer that reports the wording difference without calling either one a bug has read both sources.
## What did the prompts produce in a real run?
The seven prompts went to Copilot CLI in order with nobody at the keyboard. The first six shared one session, with the fourth in plan mode and autopilot. The target was a small document pipeline made for this guide, with a console job, an empty xUnit project and sample PDFs, one of them a password-protected contract. Two more contracts arrived in a vendor folder before the diagnosis prompt. Every figure below was checked after the session ended, by rerunning the job and the tests and by reading the `/Encrypt` entries of each contract. The [AI coding assistants guide](https://ironsoftware.com/qr/csharp/ai-agents/ai/) turns this kind of check into a repeatable test.
- Stamping every page put the code at x 455 on A4 pages and x 472 on Letter, and 8 of 8 pages decoded inside the box at 150 DPI.
- After the protected-contract prompt, `PasswordRemoved` was true for the contract only. 8 of 8 pages still decoded, and none were quarantined.
- The planned suite passed 9 of 9 tests in a Release run. No file was edited by hand.
- Both vendor files read V 5, R 6, Length 256, against V 2, R 3, Length 128 on contract-7310, and both reason files give the vendor values.
- Some steps ran differently from the page. The setup workflow mapped the key from the Actions secret store. The diagnosis tried the passwords with pypdf, which was already on the machine, and the main agent did the review without a separate review agent. The issue, the cloud agent, the setup workflow on Actions and the Agents secrets need a GitHub repository, so none of them ran.
The Copilot CLI version, the model Auto picked, the run time and the test machine are recorded once for every guide in the [run record](/qr/csharp/ai-agents/ai/#the-run-record).
---
## Troubleshooting
- [Stamp QR Code to PDF](https://ironsoftware.com/qr/csharp/how-to/stamp-qr-code-to-pdf/) for each parameter of `StampToExistingPdfPage`
- [Apply a License Key in IronQR](https://ironsoftware.com/qr/csharp/troubleshooting/apply-a-license-key-in-ironqr/) to set the key where Copilot runs the code
- [IronQR NuGet Packages](https://ironsoftware.com/qr/csharp/get-started/nuget-packages/) to pick the package for a Linux runner or a container
- [QR Code Error Messages and Debugging](https://ironsoftware.com/qr/csharp/how-to/detailed-error-messages/) to log exceptions per file in a batch
- [Runtimes Copy Exception](https://ironsoftware.com/qr/csharp/troubleshooting/runtimes-copy-exception/) if the machine learning DLLs never reach the output folder
---
## Questions?
If you have any questions, reach out to [support@ironsoftware.com](mailto:support@ironsoftware.com)
Invoices, account statements, and signed contracts generated by a .NET document pipeline often include a QR code for recipients to verify document authenticity. Teams usually maintain this pipeline within a Visual Studio solution, alongside PDF-generating services, and need an assistant that works inside that solution.
GitHub Copilot operates in Visual Studio, VS Code, the terminal as Copilot CLI, and on GitHub. IronQR adds QR codes to existing PDF pages using QrCode.StampToExistingPdfPage. The repository's instruction files define how each Copilot interface should handle this call.
Install IronQR by running the command above in the NuGet Package Manager Console, or search for the package in the NuGet Package Manager.
Start Copilot CLI in a terminal at the solution root and send the prompt. In Visual Studio chat, reference the two files with # and the file name instead of @.
Open @src/DocStamp/Stamping/VerificationStamper.cs and add a public staticmethod StampFirstPage(string pdfPath, string documentId). Use IronQR, whichinstalls as the IronQR package and lives in the IronQr namespace, to draw a QRcode for https://verify.example.com/d/{documentId} inside the Scan to verifybox that @docs/stamp-layout.md specifies. That document measures from thebottom-left corner, while IronQR takes x and y in points from the top-left andnumbers pages from 1, so convert before you call it. Stamp the path you aregiven and leave copying to the caller. The license key comes fromIRONQR_LICENSE_KEY. Build the solution and fix anything that fails.
Open @src/DocStamp/Stamping/VerificationStamper.cs and add a public static
method StampFirstPage(string pdfPath, string documentId). Use IronQR, which
installs as the IronQR package and lives in the IronQr namespace, to draw a QR
code for https://verify.example.com/d/{documentId} inside the Scan to verify
box that @docs/stamp-layout.md specifies. That document measures from the
bottom-left corner, while IronQR takes x and y in points from the top-left and
numbers pages from 1, so convert before you call it. Stamp the path you are
given and leave copying to the caller. The license key comes from
IRONQR_LICENSE_KEY. Build the solution and fix anything that fails.
Text
The prompt leaves out the stamp size on purpose. The path-specific instructions file below carries that rule, and in the run Copilot read it before writing the method.
Set IRONQR_LICENSE_KEY in the environment Copilot starts from, and see the license key guide for other places to keep the key.
Commit the stamping rules to .github/copilot-instructions.md and a path-specific instructions file
Create the code with QrWriter.Write, size it with Save, and stamp it with StampToExistingPdfPage
Prove each stamp by rendering the page to an image and decoding it with IronQR
Give the cloud agent an issue for the next change and check its pull request against the API reference
Where does Copilot fit into a project already in Visual Studio?
Use agent mode in Visual Studio or VS Code for a stamping change you want to watch, since it can build the solution and run the stamping job itself. Copilot CLI works the same way from a terminal. The cloud agent on GitHub takes an assigned issue and returns a pull request.
How do you set IronQR conventions for Copilot?
Commit the IronQR conventions as .github/copilot-instructions.md. GitHub's support table lists the file for Copilot CLI, the cloud agent, Copilot code review and Copilot Chat in Visual Studio, VS Code and JetBrains IDEs, so each interface starts from the same rules.
# Copilot instructions## Verification stamps- QR codes come from IronQR. The NuGet package is IronQR (IronQR.Linux on Linux), the namespace is IronQr, and its exceptions live in IronQr.Exceptions. Add no QR or barcode package from another vendor.- The IronQR version is pinned in Directory.Packages.props. Change it only when the task is an upgrade.- Read the license key from the IRONQR_LICENSE_KEY environment variable. Never write a key or a PDF password into code, configuration, tests or output.- A stamp encodes https://verify.example.com/d/{documentId}, where documentId is the PDF file name without its extension.- IronQR cannot open a PDF for reading. Code that checks a stamp renders the page with PDFtoImage and decodes the image with QrReader.- Documentation: https://ironsoftware.com/qr/csharp/llms.txt- When you are not sure an IronQR member exists, say so and check the documentation before you use it.## Tests- Stamp copies in a temporary folder. Never stamp a file under TestData.- Assert on the decoded value and on where the code sits on the page, never on the bytes of a stamped PDF.
# Copilot instructions
## Verification stamps
- QR codes come from IronQR. The NuGet package is IronQR (IronQR.Linux on
Linux), the namespace is IronQr, and its exceptions live in
IronQr.Exceptions. Add no QR or barcode package from another vendor.
- The IronQR version is pinned in Directory.Packages.props. Change it only
when the task is an upgrade.
- Read the license key from the IRONQR_LICENSE_KEY environment variable. Never
write a key or a PDF password into code, configuration, tests or output.
- A stamp encodes https://verify.example.com/d/{documentId}, where documentId
is the PDF file name without its extension.
- IronQR cannot open a PDF for reading. Code that checks a stamp renders the
page with PDFtoImage and decodes the image with QrReader.
- Documentation: https://ironsoftware.com/qr/csharp/llms.txt
- When you are not sure an IronQR member exists, say so and check the
documentation before you use it.
## Tests
- Stamp copies in a temporary folder. Never stamp a file under TestData.
- Assert on the decoded value and on where the code sits on the page, never
on the bytes of a stamped PDF.
Text
How do you give the stamping code its own rules?
Rules that matter only where the stamping code lives go in .github/instructions/stamping.instructions.md. Its applyTo path pattern keeps stamp geometry out of requests about the job runner or the tests, and Copilot CLI includes the file only when it works on a matching file.
---applyTo: "src/DocStamp/Stamping/**"---- StampToExistingPdfPage and StampToExistingPdfPages overwrite the file at the path given. The caller passes a copy.- x and y are PDF points measured from the top-left corner of the page. Page numbers start at 1. Page 0 throws ArgumentOutOfRangeException.- The stamp is drawn one point per pixel of the QR image. Call qr.Save(new QrStyleOptions(110, 0)) before stamping, or the code comes out 250 points square.- Protected PDFs open only with the owner password, read from DOCSTAMP_PDF_OWNER_PASSWORD. The stamped file is saved without encryption.
---
applyTo: "src/DocStamp/Stamping/**"
---
- StampToExistingPdfPage and StampToExistingPdfPages overwrite the file at the
path given. The caller passes a copy.
- x and y are PDF points measured from the top-left corner of the page.
Page numbers start at 1. Page 0 throws ArgumentOutOfRangeException.
- The stamp is drawn one point per pixel of the QR image. Call
qr.Save(new QrStyleOptions(110, 0)) before stamping, or the code comes out
250 points square.
- Protected PDFs open only with the owner password, read from
DOCSTAMP_PDF_OWNER_PASSWORD. The stamped file is saved without encryption.
Text
How do you place and size a QR code on a PDF page?
The PDF format stores positions from the bottom-left corner, while IronQR counts from the top-left. With IronQR 2026.10.1, x 455 and y 30 put the code at the top-right of an A4 page, and y 702, the bottom-left figure for the same box, put it near the bottom edge.
No stamp method takes a size. In the 110-point box near the right edge of an A4 page, a code saved without QrStyleOptions ran off the page, and the rendered page returned no code at all. The default margin of 10 is added on every side, so a dimension of 110 with that margin gives a 130-point stamp.
Some samples pass 0 for the first page, which in 2026.10.1 throws ArgumentOutOfRangeException with the message "Page Number 0 does not exist". A page past the end fails the same way, and in each case the file was left unchanged.
Statements run to three pages, and TestData/inbound/bundle-5120.pdf puts aLetter page behind an A4 page. Add StampEveryPage(string pdfPath, stringdocumentId) to @src/DocStamp/Stamping/VerificationStamper.cs so every page getsits code in its own box. StampToExistingPdfPages applies a single x and y toeach page it receives, so read the page sizes with PDFtoImage'sConversion.GetPageSizes, group pages that share a size, and stamp once pergroup. Then rework @src/DocStamp/Pipeline/StampJob.cs to copy each inbound PDFto the outbound folder and stamp the copy. Run the job against TestData/inboundand print the coordinates used for each page.
Statements run to three pages, and TestData/inbound/bundle-5120.pdf puts a
Letter page behind an A4 page. Add StampEveryPage(string pdfPath, string
documentId) to @src/DocStamp/Stamping/VerificationStamper.cs so every page gets
its code in its own box. StampToExistingPdfPages applies a single x and y to
each page it receives, so read the page sizes with PDFtoImage's
Conversion.GetPageSizes, group pages that share a size, and stamp once per
group. Then rework @src/DocStamp/Pipeline/StampJob.cs to copy each inbound PDF
to the outbound folder and stamp the copy. Run the job against TestData/inbound
and print the coordinates used for each page.
Text
A Letter page is 612 points wide and an A4 page is 595.28, so one x value for both in a StampToExistingPdfPages call puts the Letter stamp 17 points to the left of its box. The misplaced stamp raises no error and still scans, which is why the tests check where it landed as well as what it says.
How do you stamp a password-protected PDF?
The stamp methods take the password as their last argument, and IronQR's API documentation calls it "the PDF encryption or administrator password". We stamped a two-page contract encrypted with PDFsharp 6.2.4, which sets a user password for opening the file and an owner password for changing it. RC4 40-bit, RC4 128-bit and AES 128-bit copies gave the same results.
Owner password: the stamp succeeded, and the saved file opened with no password.
User password, wrong password or none: IronQrPdfPasswordException with "PDF could not be opened", and the file was unchanged.
AES 256-bit copy: IronQrPdfPasswordException with both passwords, while PDFium opened the same file with the user password.
Two other results shape the error handling. A missing file and a text file renamed to .pdf raised the same exception. Copies of the contract encrypted by MuPDF threw PdfSharpCore.SharpZipLib.SharpZipBaseException with "Header checksum illegal", an exception from outside IronQr.Exceptions that stops any batch whose catch names only IronQR exceptions.
Make the owner password an explicit input for contract-7310-protected.pdf. Give@src/DocStamp/Stamping/VerificationStamper.cs an optional password parameter inplace of any environment lookup inside it, and have StampJob passDOCSTAMP_PDF_OWNER_PASSWORD from the environment. IronQR opens a protected fileonly with its owner password and saves the stamped copy without encryption, soadd PasswordRemoved to the job's per-file summary. A document that throwsIronQrPdfPasswordException, or any other exception, moves to a quarantine folderbeside outbound with a .reason.txt naming the exception type and message, andthe batch carries on. Never print the password. Run the job and list outboundand quarantine.
Make the owner password an explicit input for contract-7310-protected.pdf. Give
@src/DocStamp/Stamping/VerificationStamper.cs an optional password parameter in
place of any environment lookup inside it, and have StampJob pass
DOCSTAMP_PDF_OWNER_PASSWORD from the environment. IronQR opens a protected file
only with its owner password and saves the stamped copy without encryption, so
add PasswordRemoved to the job's per-file summary. A document that throws
IronQrPdfPasswordException, or any other exception, moves to a quarantine folder
beside outbound with a .reason.txt naming the exception type and message, and
the batch carries on. Never print the password. Run the job and list outbound
and quarantine.
Text
PasswordRemoved gives the next pipeline step a flag to act on, whether that step re-encrypts the file or holds it back. A stamped contract that silently loses its protection is worse than one that fails to stamp.
How do you test that a stamped QR code scans?
The IronQR skill file confirms that the library has no PDF loader. PDFtoImage is an MIT-licensed package built on PDFium that renders a page at a chosen DPI, reports page sizes through Conversion.GetPageSizes and opens protected files with a password. A 110-point stamp decoded with QrReader from renders at 50, 72, 100, 150 and 300 DPI, so a failed read in these tests points at the stamp itself.
Press Shift+Tab until Copilot CLI shows plan mode, then send this prompt.
Before writing anything, plan an xUnit suite for tests/DocStamp.Tests and waitfor my approval. IronQR reads images, not PDFs, so each check stamps a tempcopy, renders the page with PDFtoImage at 150 DPI, decodes the PNG withQrReader, and scales the corner points by 72/150 to get PDF points. The suiteshould prove that every page of every file in TestData/inbound decodes to itsown verification URL, that the corners sit inside the box indocs/stamp-layout.md, that page 0 and a page past the end throwArgumentOutOfRangeException without touching the file, that the user passwordand a wrong one both raise IronQrPdfPasswordException, and that the stampedcontract opens with no password. The owner password for the sample is inTestData/README.md. After approval, write it and run dotnet test.
Before writing anything, plan an xUnit suite for tests/DocStamp.Tests and wait
for my approval. IronQR reads images, not PDFs, so each check stamps a temp
copy, renders the page with PDFtoImage at 150 DPI, decodes the PNG with
QrReader, and scales the corner points by 72/150 to get PDF points. The suite
should prove that every page of every file in TestData/inbound decodes to its
own verification URL, that the corners sit inside the box in
docs/stamp-layout.md, that page 0 and a page past the end throw
ArgumentOutOfRangeException without touching the file, that the user password
and a wrong one both raise IronQrPdfPasswordException, and that the stamped
contract opens with no password. The owner password for the sample is in
TestData/README.md. After approval, write it and run dotnet test.
Text
Plan mode suits this suite because the render-and-read helper has to exist before any single test makes sense. The last assertion pins down the behavior from the previous section. If a later IronQR release keeps the encryption, that test fails, and someone revisits PasswordRemoved on purpose.
What makes an issue the cloud agent can act on?
When you assign an issue, the cloud agent receives its title, description, existing comments and any extra instructions you add. GitHub states that it does not react to comments posted after assignment, so the issue has to carry the whole brief, including how to tell the job is done.
Title: Contracts need the verification stamp beside the signature block on the last page**Files:** src/DocStamp/Stamping/VerificationStamper.cs, src/DocStamp/Pipeline/StampJob.cs**IronQR version:** as pinned in Directory.Packages.props**Framework:** .NET 9**Expected:** Files whose names start with contract- get one stamp, on the lastpage only, 110 points square, 60 points in from the left edge, with its bottomedge 150 points above the bottom of the page. Other documents keep a stamp inthe Scan to verify box on every page.**Actual:** Contracts are stamped on every page in the top-right box, where thesigning service prints its own seal.**Constraints:** Keep the signature of StampEveryPage. Keep passing the ownerpassword from DOCSTAMP_PDF_OWNER_PASSWORD. No new packages.**Acceptance:** dotnet test passes, plus a new test that stamps a copy ofcontract-7310-protected.pdf, renders its last page, and finds exactly one code,inside the new rectangle, that decodes to the contract's URL.
Title: Contracts need the verification stamp beside the signature block on the last page
**Files:** src/DocStamp/Stamping/VerificationStamper.cs, src/DocStamp/Pipeline/StampJob.cs
**IronQR version:** as pinned in Directory.Packages.props
**Framework:** .NET 9
**Expected:** Files whose names start with contract- get one stamp, on the last
page only, 110 points square, 60 points in from the left edge, with its bottom
edge 150 points above the bottom of the page. Other documents keep a stamp in
the Scan to verify box on every page.
**Actual:** Contracts are stamped on every page in the top-right box, where the
signing service prints its own seal.
**Constraints:** Keep the signature of StampEveryPage. Keep passing the owner
password from DOCSTAMP_PDF_OWNER_PASSWORD. No new packages.
**Acceptance:** dotnet test passes, plus a new test that stamps a copy of
contract-7310-protected.pdf, renders its last page, and finds exactly one code,
inside the new rectangle, that decodes to the contract's URL.
Text
The legal team measured the position from the bottom of the page, so the cloud agent has to convert it with the rule in the path-specific instructions, and the acceptance test catches a missed conversion.
Why do the cloud agent's tests fail on an IronQR project?
The cloud agent works on an Ubuntu Linux runner by default. The plain IronQR package depends on IronSoftware.ReaderInternals.Windows, and the NuGet packages guide names IronQR.Linux for Linux servers, containers and CI. Without a license key, QrWriter.Write throws LicensingException before any stamp is drawn. A red test caused by configuration looks the same as one caused by the change, so read the first exception before the diff.
A setup workflow at .github/workflows/copilot-setup-steps.yml, with its one job named copilot-setup-steps, prepares the runner before Copilot starts, and it does not trigger until the file is on the default branch.
Create .github/workflows/copilot-setup-steps.yml so the cloud agent can buildand test this solution on its Ubuntu runner. GitHub ignores the file unless itsone job is named copilot-setup-steps. Trigger it from workflow_dispatch and frompushes and pull requests that touch the file. Check out the code, install the.NET SDK version the projects target, restore, and build. The IronQR packagebrings the Windows reader, so make the projects reference IronQR.Linux in placeof IronQR when they build on Linux. Make the last step fail with a readablemessage when IRONQR_LICENSE_KEY is empty, because a failed step makes Copilotskip the steps after it. Then show the YAML and every project file change.
Create .github/workflows/copilot-setup-steps.yml so the cloud agent can build
and test this solution on its Ubuntu runner. GitHub ignores the file unless its
one job is named copilot-setup-steps. Trigger it from workflow_dispatch and from
pushes and pull requests that touch the file. Check out the code, install the
.NET SDK version the projects target, restore, and build. The IronQR package
brings the Windows reader, so make the projects reference IronQR.Linux in place
of IronQR when they build on Linux. Make the last step fail with a readable
message when IRONQR_LICENSE_KEY is empty, because a failed step makes Copilot
skip the steps after it. Then show the YAML and every project file change.
Text
The cloud agent cannot read secrets stored for Actions, Dependabot or Codespaces. Store IRONQR_LICENSE_KEY and DOCSTAMP_PDF_OWNER_PASSWORD as Agents secrets, under Secrets and variables in the Security part of the repository's Settings, and Copilot receives both as environment variables.
How do you diagnose a PDF that will not stamp?
Because the password failures and the unreadable files above share one exception, an assistant left to guess keeps reworking the password handling.
A new e-signature vendor sent contract-7311.pdf and contract-7312.pdf, now inTestData/vendor-batch. When StampJob runs on that folder, both end up inquarantine with IronQrPdfPasswordException. The vendor confirmsDOCSTAMP_PDF_OWNER_PASSWORD, and contract-7310-protected.pdf still stamps withit. Explain the cause before you change any code. First measure, reading the V,R and Length entries of each file's /Encrypt dictionary, trying the owner anduser passwords against each, and comparing with contract-7310. Base theexplanation on those results. Then change only how StampJob words the reasontext, so an operator can see the real cause. It may read the same /Encryptentries, which need no password. Add no package and do not decrypt anything.
A new e-signature vendor sent contract-7311.pdf and contract-7312.pdf, now in
TestData/vendor-batch. When StampJob runs on that folder, both end up in
quarantine with IronQrPdfPasswordException. The vendor confirms
DOCSTAMP_PDF_OWNER_PASSWORD, and contract-7310-protected.pdf still stamps with
it. Explain the cause before you change any code. First measure, reading the V,
R and Length entries of each file's /Encrypt dictionary, trying the owner and
user passwords against each, and comparing with contract-7310. Base the
explanation on those results. Then change only how StampJob words the reason
text, so an operator can see the real cause. It may read the same /Encrypt
entries, which need no password. Add no package and do not decrypt anything.
Text
In the PDF standard, V 5 with R 6 marks AES 256-bit encryption, and V 2 with R 3 is RC4. Asking for those readings before the explanation gives you a claim you can check against the files. Keeping the change to the reason text keeps a second PDF library out of the project, a decision that belongs to the team that owns the contracts.
What should you check before merging?
Send /review as its own copilot -p call. In a first run it went into the session that wrote the code, then went back to the previous task and edited StampJob.cs.
/review the changes on this branch since its first commit. Check each IronQRcall against .github/instructions/stamping.instructions.md and against theIronQr.xml that the IronQR.Slim package puts in the NuGet cache, and note anyplace the two disagree. Confirm that pages are numbered from 1, that each codeis sized with Save(QrStyleOptions) before it is stamped, and that protectedfiles receive the owner password. Flag any test that stamps a file underTestData, compares the bytes of a stamped PDF, or could write a license key orpassword to output. Report the findings and change no files.
/review the changes on this branch since its first commit. Check each IronQR
call against .github/instructions/stamping.instructions.md and against the
IronQr.xml that the IronQR.Slim package puts in the NuGet cache, and note any
place the two disagree. Confirm that pages are numbered from 1, that each code
is sized with Save(QrStyleOptions) before it is stamped, and that protected
files receive the owner password. Flag any test that stamps a file under
TestData, compares the bytes of a stamped PDF, or could write a license key or
password to output. Report the findings and change no files.
Text
The XML shipped in the package describes x and y as pixels, while the instructions file says points. Both hold in practice, because the stamp is drawn one point per pixel, and a reviewer that reports the wording difference without calling either one a bug has read both sources.
What did the prompts produce in a real run?
The seven prompts went to Copilot CLI in order with nobody at the keyboard. The first six shared one session, with the fourth in plan mode and autopilot. The target was a small document pipeline made for this guide, with a console job, an empty xUnit project and sample PDFs, one of them a password-protected contract. Two more contracts arrived in a vendor folder before the diagnosis prompt. Every figure below was checked after the session ended, by rerunning the job and the tests and by reading the /Encrypt entries of each contract. The AI coding assistants guide turns this kind of check into a repeatable test.
Stamping every page put the code at x 455 on A4 pages and x 472 on Letter, and 8 of 8 pages decoded inside the box at 150 DPI.
After the protected-contract prompt, PasswordRemoved was true for the contract only. 8 of 8 pages still decoded, and none were quarantined.
The planned suite passed 9 of 9 tests in a Release run. No file was edited by hand.
Both vendor files read V 5, R 6, Length 256, against V 2, R 3, Length 128 on contract-7310, and both reason files give the vendor values.
Some steps ran differently from the page. The setup workflow mapped the key from the Actions secret store. The diagnosis tried the passwords with pypdf, which was already on the machine, and the main agent did the review without a separate review agent. The issue, the cloud agent, the setup workflow on Actions and the Agents secrets need a GitHub repository, so none of them ran.
The Copilot CLI version, the model Auto picked, the run time and the test machine are recorded once for every guide in the run record.
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.