You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ci: run the examples and the README C# code against the API - #93
A new workflow, .github/workflows/examples.yml ("Examples"), runs the C# examples and the C# code in README.md against the ConvertAPI API, at https://v2.convertapi.com, the address the SDK uses by default. It uses a token kept for CI, the CONVERTAPI_CI_TOKEN secret, and runs on ubuntu-latest and macos-latest:
for pull requests to main and pushes to main that change ConvertApi/, Examples/, README.md, global.json or the CI files, so unrelated changes cost no conversions;
every Monday at 05:17 UTC, because the API can break an example without a commit here, as the removed PDF-thumbnail parameter and the blocked URL-to-PDF page did;
on demand.
Pull requests from forks are skipped, because they get no secrets. Their changes run on main after the merge.
Each job:
builds the solution and runs all 21 examples in Examples/. Each example runs from an empty folder with stdin closed, so its Console.ReadLine() returns at once;
makes a console project of each of the 44 csharp blocks in README.md and builds them all;
runs the quick start and the 10 blocks under the eight "... in C#" task headings. The other 33 blocks are only built.
One job converts about 40 files, so a workflow run uses about 80 conversions. build.yml is unchanged: it still compiles the examples, and build stays the only required check.
What fails the job
An example exits with an error, or prints an API error it caught. The examples catch ConvertApiException, print Status Code: and Response:, and exit with 0. ExceptionHandling is the one exception: it must print Status Code: BadRequest, the error it is written to provoke.
A README block does not build. Each compiler error is annotated on the README.md line it comes from.
The quick start or a task block exits with an error or saves no file. The text-extraction block must print text instead.
Any output contains the API token, or a Secret or ApiKey field, because Actions logs of a public repository are public. A leak fails at once, without a retry, and its line is not printed.
A program was built without the CI host below, so nothing deleted its files from the server.
A failed run is tried again up to twice, 30 s apart, for a moment when the API answers 503. Retries show as warnings, so a flaky run stays visible. Program output is printed with workflow commands switched off. Each job writes a table of every example and README block to the run's summary page.
The CI host, and the files left on the server
The programs run exactly as people copy them, against the same API address. The job sets the environment variable CustomAfterMicrosoftCommonTargets to .github/ci/ci-host.targets. MSBuild reads environment variables as properties, and Microsoft.Common.targets imports the file this property names, so every project the job builds imports it. Nothing in the repository imports it: normal builds, and every folder people copy, are unchanged.
The targets file adds .github/ci/CiHost.cs to the programs only (OutputType Exe, not test projects), never to the library or the unit tests. CiHost.cs is a module initializer, so it runs before Main. It:
prints the API address in a [ci] line, which the checks require;
gives the SDK an HTTP client with the same settings as its default one: an HttpClient with ConvertApiConstants.HttpClientTimeOut, made on first use. Its handler notes the URL of every file the API returns for an upload or a conversion. When the program exits, the host deletes those files from the server, as DeleteFilesAsync does, instead of leaving them for up to 3 hours. A run that leaves files gets a warning, not a failure. The client goes in through the SDK's public new ConvertApi(token, client) constructor, with the current token (none yet), so each program still sets its own token;
if a program crashes on a ConvertApiException, prints the API's response, which the crash message leaves out, for example which parameter value the API rejected.
Why this and not the alternatives:
A Directory.Build.props or .targets in Examples/ would sit in the folder people copy, and would not reach the README projects, which are made outside the repository.
Adding DeleteFilesAsync calls or other CI code to the examples and README blocks would change the code people copy.
.github/ci/check_csharp.py readme-build extracts every csharp block. Each complete program (one with using ConvertApiDotNet;) becomes Program.cs of a project made by dotnet new console. The project gets a ProjectReference to ConvertApi/ConvertApi.csproj instead of the NuGet package, so README.md is checked against what the next release ships, not against the version on nuget.org.
The three fragments are spliced into the quick start program and built:
README section
Fragment
How it is built
Quick start
the URL variant, var result = ... new Uri(...)
replaces the quick start's var result statement
Set the API token
var convertApi = new ConvertApi(...)
replaces the quick start's var convertApi statement
goes before the quick start's first statement; built, never run
No fragment is skipped today. A later fragment that is neither a quick start statement nor a ConvertApi setting is listed as skipped in the job log and on the summary page, with a notice on its README line.
The runs copy sample files from Examples/TestFiles in under the names the blocks use: document.docx (test.docx), document.pdf (test.pdf), page.html (test.html, new) and spreadsheet.xlsx (test.xlsx, new). The two new samples are small hand-written files: an HTML page with a table, and a one-sheet workbook with the same table.
Results so far
The same checks ran locally on macOS against the staging API on 2026-11-11, before the switch to production:
21 of 21 examples passed;
44 of 44 README blocks built (41 programs and 3 spliced fragments, none skipped, no warnings), and 11 of 11 runs passed;
with temporary edits that are not in this pull request, the job failed as it should: on a compile error in a README block (annotated on its line), on an invalid parameter value (after three attempts, with the API's 400 response), on an example that printed a Secret= line (at once, with the line hidden), and on ExceptionHandling no longer getting its 400.
After the switch, checked locally without converting anything:
all 44 README blocks build;
the CI host is in all 21 examples and in no library or test assembly;
a program run without a token prints the [ci] lines and the SDK's own missing-token error;
the API accepts the new CI token.
The first CI run built everything on both runners and stopped only at the missing secret. The production runs follow once the secret exists.
Before this can go green
The job needs the CONVERTAPI_CI_TOKEN secret twice: as an Actions secret, and as a Dependabot secret, because Dependabot runs read only Dependabot secrets. Until then, each job builds everything and then stops at "Check the API token" with a clear message. pull_request_target is not used.
That step also masks the account's name and email in the log. check_csharp.py hides the account fields that RetrieveUserInformation prints: name, email and conversion counts.
Files
.github/workflows/examples.yml: the workflow.
.github/ci/ci-host.targets and .github/ci/CiHost.cs: the CI host, CI only.
.github/ci/check_csharp.py: runs and checks the examples and the README blocks, with the Python 3 standard library only. Its docstring says how to run it locally.
Examples/TestFiles/test.html and Examples/TestFiles/test.xlsx: samples for the HTML and Excel README blocks.
Not changed: README.md, the library, CHANGELOG.md, RELEASING.md and build.yml.
A new workflow, examples.yml, runs on ubuntu-latest and macos-latest for pull
requests, pushes to main, every Monday and on demand:
- builds the solution and runs all 21 examples against https://stag-v2.convertapi.com;
- makes a console project of each of the 44 C# blocks in README.md, referencing
the library source, and builds them all (fragments are spliced into the quick
start program);
- runs the quick start and the blocks under the "... in C#" task headings with
sample files from Examples/TestFiles;
- fails when a program fails, prints a caught API error, saves nothing, or
prints the token or a Secret or ApiKey field.
The programs stay as people copy them: MSBuild imports
.github/ci/staging-host.targets only when CI sets CustomAfterMicrosoftCommonTargets,
and it compiles in StagingHost.cs, a module initializer that sets
ConvertApi.ApiBaseUri before Main. A failed run is retried twice, 30 s apart.
Examples/TestFiles gains test.html and test.xlsx for the HTML and Excel
README snippets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Quoted JSON credentials can bypass detection and leak into public logs, while a leftover staging diagnostic lets text-extraction checks pass without any extracted text.
Run the examples and the README code against the API people use, with a
token kept for CI, instead of the staging API:
- the programs call https://v2.convertapi.com, the SDK's default; the CI
host (CiHost.cs, was StagingHost.cs) no longer changes the address;
- CiHost.cs records every uploaded and result file and deletes it from the
server when the program exits, instead of leaving it for 3 hours;
- the token comes from the CONVERTAPI_CI_TOKEN secret, an Actions and a
Dependabot secret;
- the workflow runs for changes to ConvertApi/, Examples/, README.md,
global.json or the CI files, every Monday and on demand; pull requests
from forks are skipped;
- the account details that RetrieveUserInformation prints stay out of the
public log.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tomasr78
changed the title
ci: run the examples and the README C# code against the staging API
ci: run the examples and the README C# code against the API
Nov 11, 2026
A JSON line such as "Secret": "..." has a quote between the key and the
colon, so the pattern missed it, both in the leak check and in the log
printer. Allow an optional quote after the key, and the same for the
account fields the log hides.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Quoted JSON credentials can bypass detection and redaction, exposing secrets in public CI logs, while the workflow incorrectly targets production and skips required fork build checks.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Detected multiline credentials can still appear in public logs, and timed-out child processes can continue making paid API calls and writing files during retries.
Terminate child processes on timeout before retrying
.github/ci/check_csharp.py:186
On timeout, subprocess.run() kills only its immediate process, but both callers use dotnet run, which launches the example as a child. That child can continue making paid API calls and writing files while a retry resets the output folder or starts another run. Start a separate process group and terminate and reap it before retrying, or run the built program directly.
- When a run's output contains the token or a Secret or ApiKey field, none
of it is printed: a value can sit on a line of its own after its key.
- A run that times out is stopped with its whole process group. `dotnet run`
starts the program as its own child, which could otherwise go on calling
the API while the next attempt runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
A new workflow,
.github/workflows/examples.yml("Examples"), runs the C# examples and the C# code inREADME.mdagainst the ConvertAPI API, at https://v2.convertapi.com, the address the SDK uses by default. It uses a token kept for CI, theCONVERTAPI_CI_TOKENsecret, and runs on ubuntu-latest and macos-latest:ConvertApi/,Examples/,README.md,global.jsonor the CI files, so unrelated changes cost no conversions;Pull requests from forks are skipped, because they get no secrets. Their changes run on main after the merge.
Each job:
Examples/. Each example runs from an empty folder with stdin closed, so itsConsole.ReadLine()returns at once;csharpblocks inREADME.mdand builds them all;One job converts about 40 files, so a workflow run uses about 80 conversions.
build.ymlis unchanged: it still compiles the examples, andbuildstays the only required check.What fails the job
ConvertApiException, printStatus Code:andResponse:, and exit with 0. ExceptionHandling is the one exception: it must printStatus Code: BadRequest, the error it is written to provoke.SecretorApiKeyfield, because Actions logs of a public repository are public. A leak fails at once, without a retry, and its line is not printed.A failed run is tried again up to twice, 30 s apart, for a moment when the API answers 503. Retries show as warnings, so a flaky run stays visible. Program output is printed with workflow commands switched off. Each job writes a table of every example and README block to the run's summary page.
The CI host, and the files left on the server
The programs run exactly as people copy them, against the same API address. The job sets the environment variable
CustomAfterMicrosoftCommonTargetsto.github/ci/ci-host.targets. MSBuild reads environment variables as properties, and Microsoft.Common.targets imports the file this property names, so every project the job builds imports it. Nothing in the repository imports it: normal builds, and every folder people copy, are unchanged.The targets file adds
.github/ci/CiHost.csto the programs only (OutputType Exe, not test projects), never to the library or the unit tests.CiHost.csis a module initializer, so it runs beforeMain. It:[ci]line, which the checks require;HttpClientwithConvertApiConstants.HttpClientTimeOut, made on first use. Its handler notes the URL of every file the API returns for an upload or a conversion. When the program exits, the host deletes those files from the server, asDeleteFilesAsyncdoes, instead of leaving them for up to 3 hours. A run that leaves files gets a warning, not a failure. The client goes in through the SDK's publicnew ConvertApi(token, client)constructor, with the current token (none yet), so each program still sets its own token;ConvertApiException, prints the API's response, which the crash message leaves out, for example which parameter value the API rejected.Why this and not the alternatives:
Directory.Build.propsor.targetsinExamples/would sit in the folder people copy, and would not reach the README projects, which are made outside the repository.DeleteFilesAsynccalls or other CI code to the examples and README blocks would change the code people copy.It is the .NET counterpart of the Node SDK's
node --require ./.github/examples-base-uri.jspreload (https://github.com/ConvertAPI/convertapi-library-node/blob/main/.github/workflows/build.yml).README.md blocks
.github/ci/check_csharp.py readme-buildextracts everycsharpblock. Each complete program (one withusing ConvertApiDotNet;) becomesProgram.csof a project made bydotnet new console. The project gets aProjectReferencetoConvertApi/ConvertApi.csprojinstead of the NuGet package, so README.md is checked against what the next release ships, not against the version on nuget.org.The three fragments are spliced into the quick start program and built:
var result = ... new Uri(...)var resultstatementvar convertApi = new ConvertApi(...)var convertApistatementConvertApi.ApiBaseUri = "https://eu-v2.convertapi.com";No fragment is skipped today. A later fragment that is neither a quick start statement nor a
ConvertApisetting is listed as skipped in the job log and on the summary page, with a notice on its README line.The runs copy sample files from
Examples/TestFilesin under the names the blocks use:document.docx(test.docx),document.pdf(test.pdf),page.html(test.html, new) andspreadsheet.xlsx(test.xlsx, new). The two new samples are small hand-written files: an HTML page with a table, and a one-sheet workbook with the same table.Results so far
Secret=line (at once, with the line hidden), and on ExceptionHandling no longer getting its 400.[ci]lines and the SDK's own missing-token error;Before this can go green
The job needs the
CONVERTAPI_CI_TOKENsecret twice: as an Actions secret, and as a Dependabot secret, because Dependabot runs read only Dependabot secrets. Until then, each job builds everything and then stops at "Check the API token" with a clear message.pull_request_targetis not used.That step also masks the account's name and email in the log.
check_csharp.pyhides the account fields that RetrieveUserInformation prints: name, email and conversion counts.Files
.github/workflows/examples.yml: the workflow..github/ci/ci-host.targetsand.github/ci/CiHost.cs: the CI host, CI only..github/ci/check_csharp.py: runs and checks the examples and the README blocks, with the Python 3 standard library only. Its docstring says how to run it locally.Examples/TestFiles/test.htmlandExamples/TestFiles/test.xlsx: samples for the HTML and Excel README blocks.Not changed: README.md, the library, CHANGELOG.md, RELEASING.md and build.yml.
🤖 Generated with Claude Code