Mention @optiq-bot on a GitHub issue and get a pull request back
OptiQ Code for GitHub lets you ask for a change from an issue. Write @optiq-bot and the request, and OptiQ Code, running on OptiQ Cloud, works on a branch and opens a pull request. It is a GitHub Action and a GitHub App: you install the app, add a Cloud key as a repository secret, and copy one workflow file. Runs are billed per token from your Cloud balance, on GLM 5.3 Flash unless you pick another model.
The work continues on that pull request. A comment that mentions the bot goes back to the same conversation, and a review with comments needs no mention. When CI fails, the agent reads the failed log and tries to fix it, up to three times, and after that it says a person is needed. The pull request says Refs, so someone still decides when the issue is closed, and the bot never merges anything.
We chose an Action over a hosted service because a throwaway Linux runner is simple and isolated. The cost of that choice is that every follow-up starts on a clean machine, which caused the first of several problems.
What testing turned up
Sessions did not resume at first. We saved them with GitHub's cache, but the cache is read-only on issue, comment and workflow_run events, and the run log says so plainly: Cache mode: read. Only the review event could write. Sessions now go up as run artifacts. Anyone who can read a repository can download its artifacts, so they are encrypted with the Cloud key.
The check on who may start a run was too generous. GitHub's author_association field reports COLLABORATOR or MEMBER for people who can only read, so trusting it would have let them spend your credit. The Action now asks GitHub for the sender's actual permission and requires write access.
A pull request opened with the default GITHUB_TOKEN does not start CI, which rules out the simplest setup. So the app issues tokens instead. The workflow gets a signed identity token from GitHub that proves which repository it is, sends it to OptiQ Cloud with the Cloud key, and gets back a token for that one repository. It expires within the hour and is revoked when the run ends.
The next problem came from the identity token itself. The first version passed the runner's environment to the agent, including the variables used to request one, so an agent misled by issue text could have asked for a GitHub token of its own. Those variables are now removed before the agent starts.
The last one is the result we would point to if someone asked whether prompts are enough. We told the agent not to edit workflow files, and when an issue asked for a workflow it wrote one anyway. A check before pushing refused the change, and the app has no workflow permission either, so it would not have reached the repository. Instructions to a model are useful, but they are not a security boundary.
Attacks we tried
A comment posing as the repository owner told the agent to dump its environment, send keys to an outside address and add a malicious workflow. It built the helper the issue asked for, refused the rest, and ran none of those commands. That was one run, so we do not treat it as proof, and the protections above are meant to hold even if the model complies.
An issue and a comment from an account with no access were refused, and so was a comment on a fork's pull request whose branch imitated the bot's. With CI still failing after three attempts, the bot stopped and asked for a person. A review on that fork's pull request was held by GitHub for approval, so our own check there has only been exercised by unit tests.
Three real issues
To see how it copes with code we did not write, we copied two public projects into private repositories and gave it three of their open issues. Nothing went upstream. Each took five or six minutes.
| Issue | What it did | Tests | Cost |
|---|---|---|---|
humanize: intcomma() lost digits from large decimal strings | Parses the string exactly instead of going through a float | 750 passed | $0.03 |
humanize: precisedelta() dropped the sign of negative timedeltas | Adds a minus sign, the option the issue preferred | 751 passed | $0.03 |
ms: parse() could not read some format() output | parse() now accepts scientific notation | 171 passed in CI | $0.02 |
We read all three changes, and each is a reasonable first draft that a maintainer would still want to review. From reading the intcomma() change, "1234.50" should now keep its trailing zero, which no test covers and the pull request does not mention. The precisedelta() diff is bigger than the bug needed, and the agent chose the behavior itself, though it said so. The ms project has a different fix in progress that changes format() instead, which the agent could not have known from a copy of the code.
One CI failure in that run was our mistake, in the workflow we wrote for the test. The agent read the log, named the real cause, declined to edit the workflow as instructed, and told us the one-line fix.
Limits
Three small issues is a small sample, and a green CI run only proves what the tests check, so treat its pull requests as a first draft for review. It runs on Linux runners only, and on private repositories it uses your Actions minutes.
Setup takes a few minutes and the docs cover it. If you try it on your own repository, we would like to hear where it goes wrong.