T08-L04 · Write code · Level 4 Integrator · 20-minute read, plus 4 hours of doing

A password in the history

The plate-reader summary from T08-L03 has become part of the floor's routine. Three groups use it, four people change it, and the institute's IT has asked for the next step: read plate results straight from the LIMS, the lab's sample-tracking system, instead of from exported files. Mira opens OpenCode and asks it to add the connection. The assistant writes a working version. To test it, it puts the LIMS login into a settings file, and at the end of the session it helpfully commits and pushes everything to the main branch. A colleague pulls the change that afternoon. The password is now in four copies of the repository's history.

In a company it's Leonie's returns report. The head of quality wants it to update the dashboard of the ERP, the company's system for orders, stock and returns, every Monday. One colleague uses OpenCode's free models and pastes a real returns export with customer names "so it understands the columns". Another merges his own change on a Friday, and on Monday the dashboard shows zero returns for March.

You are at Level 4 Integrator. The code now touches the organisation's real systems: the LIMS, the ERP, a shared repository that several people rely on. A mistake reaches real records and every colleague who pulls the code. This book shows you how to set up an AI coding assistant for that kind of repository: which model it may use, what it may touch on your computer, how each change gets a second pair of eyes, and how the code gets the smallest access that does its job.

After this you can

  • Set up OpenCode Desktop for a team repository: the approved model, sharing switched off, and permissions committed with the code.
  • Deliver each AI-assisted change on its own branch, as a pull request that someone other than the author reviews against the spec.
  • Protect the main branch so that nobody, including you and the assistant, can change it without a reviewed pull request.
  • Keep credentials out of the repository and out of the assistant's reach, using a read-only account IT creates for the job.
  • Run the tests and a secret scan on your own computer before every push, and know how to undo a merged change.

Before you start

  • Books: T08-L03 for the spec, the known-answer tests and the version tags this book builds on. T01-L01 for the four-quadrant method. T12-L02 for classifying the data the code touches.
  • A shared Git repository on your organisation's server, such as Gitea (a self-hosted service for sharing Git repositories) or GitHub, where you can change the repository's settings, or someone who can.
  • OpenCode Desktop, set up as below, and Git on your computer.
  • IT and whoever runs the system your code reads, such as the LIMS administrator or the ERP team, for an hour each.
  • A colleague who reviews your pull request.
  • About 4 hours: 20 minutes to read, the rest spread over a week.

Last verified 23 September 2026. OpenCode Desktop is a free download for macOS, Windows and Linux. A shared OpenCode session gets a public link; a project can switch sharing off. On GitHub, protecting a branch in a private repository needs a paid plan. Details for experts at the end of this book.

Set up OpenCode Desktop

Other books send you here to set up OpenCode Desktop, an app in which an AI assistant reads and edits the files in one project folder. If you already use it, skip to Set it up for team work at the end of this section.

  1. Install it. Download OpenCode Desktop from opencode.ai/download for your system and install it. On a managed work computer, ask IT to install it for you.
  2. Open a project. Start the app, choose Open project and select the folder you want to work in. Everything the assistant reads and changes is inside that folder.
  3. Connect a model. The model is the AI engine that writes the answers. The app comes with free models from OpenCode Zen, OpenCode's own model service. They're fine for practice with made-up data. For work, choose Connect provider and pick the route your lab or company has approved; IT gives you the details. Your key goes into that connection form, never into a message.
  4. Choose the model in the model selector under the message box, and send a small test: "In two sentences, what does this folder contain? Don't change anything."
  5. Turn on history. Open the Review tab, which lists every file the assistant changed. If it says no Git repository was found, click Create Git repository. Without Git you can't see or undo changes.
  6. Learn two keys. Tab switches between Plan, where the assistant proposes steps but can't change files, and Build, where it can. Typing /undo in the message box reverses the assistant's last change.

That's enough for Levels 1 to 3. Set it up for team work is the rest of this book: in a shared repository, the model, the sharing setting and the permissions live in a file called opencode.json, committed with the code, so everyone's assistant follows the same rules.

The idea in one page

On your own folder, a mistake stays with you. In a team repository connected to real systems, there are three places where one can spread. Each needs its own lock.

Where it can go wrongThe lockWho sets it
On your computer: the assistant reads a secret, sends real data to the wrong model, or pushes by itselfopencode.json in the repository: approved model, sharing off, secrets unreadable, no pushYou, reviewed like any code
On the main branch: an unreviewed change reaches everyone who pullsBranch protection: main accepts changes only through a reviewed pull requestWhoever administers the repository
On the real system: the code can read or change more than it needsA service account, a login for a program rather than a person, with read-only access to exactly what the code needsIT and the system's administrator

The assistant works with your rights. When OpenCode runs git push, it pushes as you. When it reads a file, it can read anything you can in that folder. So its limits have to be tighter than yours. By default most of its actions are allowed. For a team repository, write down what it may never do: read files with secrets, push, or reach outside the project folder.

Every change arrives as a pull request. A branch is a separate line of work; a pull request proposes merging it into main and shows a reviewer the diff, every line added or removed. The reviewer checks the change against the spec from T08-L03, rule by rule, and approves it or asks for changes. Someone other than the author approves. That's what catches a guess that looks reasonable.

A secret in Git is a secret everyone has. Deleting the file doesn't remove it from the history. Once a password has been pushed, the only fix is to have it changed. So keep secrets in a file Git ignores, keep that file out of the assistant's reach, and scan for secrets before every push.

Four questions before the code touches a real system.

QuestionWhat you write down
What's the least access?The account, what it can read, and what you proved it can't do.
What happens on failure?What the code does when the system is down or the login expires.
Who signed off?IT for the account, the system's administrator and the data owner for the data, the repository administrator for the protection.
How do you undo it?How to reverse a merged change, and who can switch the account off.

Worked example: two repositories, four quadrants

The method comes from T01-L01. The lab's repository lives on the institute's Gitea server; the company's on GitHub. People and systems are examples written for this book.

Lab: the plate summary reads from the LIMS

Structure it. Mira writes: "The plate summary should read plate results directly from the LIMS for the three groups that use it. Four people change the code. A useful result: nobody can change main without a second person's review, no password is ever in the repository, and the script can read plate results and nothing else."

Understand it. She starts with what exists. The repository's settings show that all four contributors can push straight to main, and the last month's history has eleven such pushes, two by OpenCode sessions that committed and pushed on their own. She runs a secret scan over the history, using gitleaks, a free tool that searches a repository for things that look like passwords and keys. It finds one: a LIMS test password committed in March and deleted a day later. It's still in every clone, the full copy of the repository each colleague has on their computer.

Then she meets Jonas, the LIMS administrator. The LIMS can create an API user, a login for programs, with a role limited to reading plate results for named groups. It can't see who submitted samples, patient-linked projects or anything outside those groups. The March password belongs to a real test account, which Jonas changes the same day.

The meeting changes her problem. Back to Structure it: "The LIMS connection is the easy part. The real problem is that anyone and anything can change main, a secret already got in once, and every assistant session has the same rights as its user. Fix those first, then add the connection as the first change through the new process."

Choose tools. She keeps OpenCode Desktop, which the group knows. The model is the institute's approved route: a model gateway IT runs, which passes requests to models the institute has cleared for source code. The alternative is Claude Code, which reads the same kind of rules from its own settings file.

For review and protection she uses what the institute's Gitea server already offers. For the secret scan, gitleaks, run automatically on each person's computer before every push. She doesn't yet add checks that run on the server for every pull request; those are the next book, T08-L05.

Build it: the assistant's rules (30 min). She writes opencode.json in the repository's top folder:

{
  "$schema": "https://opencode.ai/config.json",
  "share": "disabled",
  "permission": {
    "external_directory": "deny",
    "read": { "*": "allow", "*.env": "deny", "secrets/**": "deny" },
    "bash": {
      "*": "ask",
      "git status*": "allow",
      "git diff*": "allow",
      "python -m unittest*": "allow",
      "git push*": "deny",
      "git commit*": "deny"
    }
  }
}

Read from the top: the first line only tells editors where the file's format is described. Sharing is off, so no session can be published as a public link. The assistant can't open files outside the project. It can read everything except .env files and the secrets folder. It must ask before any command, except looking at changes and running the tests. It may never commit or push; people do that. When several rules match, the last one wins, which is why the catch-all "*": "ask" comes first.

The connection to the institute's gateway goes into each person's OpenCode through Connect provider, with their own key. The key stays on their computer and never enters the repository.

The LIMS login will live in a file called .env, a plain list of NAME=value lines on each person's computer. The script reads it when it starts.

She adds .env to .gitignore, the list of files Git never records, and commits a .env.example that shows the names of the settings without values:

LIMS_URL=https://lims.example-institute.de/api
LIMS_USER=plate-summary-reader
LIMS_TOKEN=

Build it: the checks before every push (20 min). A Git hook is a small script Git runs automatically at a certain moment. She commits one in a .githooks folder, named pre-push, so it runs before anything leaves her computer:

#!/bin/sh
# Runs before every push. Any failure stops the push.
python -m unittest || { echo "Tests failed: push stopped."; exit 1; }
gitleaks git --no-banner || { echo "Possible secret found: push stopped."; exit 1; }

Hooks aren't switched on when someone clones a repository, so the README tells each contributor three things to do once:

  1. Install gitleaks: with Homebrew on a Mac (brew install gitleaks), or by downloading the file for their system from the releases page of gitleaks on GitHub. On a managed computer, IT does this.
  2. Run git config core.hooksPath .githooks, which tells Git to use the hooks in that folder.
  3. On macOS and Linux, run chmod +x .githooks/pre-push, which marks the hook as a program Git may run. Git skips a hook that isn't marked this way.

The next time anyone pushes, the tests and the scan run first.

Build it: branch protection (with the Gitea administrator, 20 min). In the repository's Settings, under Branches, the administrator adds a protection rule for main and sets:

  • Disable push: nobody pushes to main directly; changes arrive only by merging a pull request.
  • Required approvals: 1, and the author's own approval doesn't count.
  • Dismiss stale approvals: if the author adds commits after approval, the reviewer has to look again.
  • Block merge on rejected reviews.
  • Apply to administrators too, so the rule holds for everyone.

Then Mira proves it. She tries git push origin main from her own computer and gets a refusal from the server. She pastes the refusal into the repository record. A rule you haven't seen refuse something is only a setting.

Build it: the first change through the new process (90 min). Jonas creates the API user plate-summary-reader and gives its token, the long password-like string a program logs in with, to Mira and to the server's service account, not by email but through the institute's password vault. Mira puts it in her .env.

She creates a branch for the change with git switch -c lims-input, which starts a new line of work without touching main. She updates SPEC.md first, as in T08-L03, where R rules say what the change must do and M rules what must not change. The new rules: R6, read plate results for one plate ID from the LIMS; R7, if the LIMS is unreachable or rejects the login, stop with a message and write nothing; M6, the existing file input keeps working unchanged. She adds known-answer tests that use a saved, made-up LIMS response, so the tests never need the real system.

In OpenCode she uses Plan first: "Read SPEC.md. Plan the change for R6 and R7. The login comes from environment variables named in .env.example. Don't read .env." The plan maps each step to a rule. In Build, the assistant writes the code and runs the tests.

Twice the rules matter. Once it tries to read .env to "check the connection"; the permission refuses, and it uses the example file instead. Once it asks to run pip install requests, which would add an outside library; she declines, because M3 in the spec says standard library only, and the assistant uses Python's built-in urllib instead.

She reviews the diff rule by rule and commits it herself. The first push of a new branch names it: git push -u origin lims-input sends the branch to Gitea and remembers where it belongs. The hook runs: tests pass, no secrets found. On the repository's page in Gitea she opens Pull requests, then New pull request, and picks lims-input to merge into main. The pull request's description says which rules it implements, that OpenCode wrote the first draft, and which model was used.

Paul reviews it. He finds one thing the tests didn't: when the LIMS returns an empty plate, the script writes an empty summary instead of stopping. That's a case R7 should cover. Mira adds a test and a fix and pushes again. Because of dismiss stale approvals, Paul reviews the new commit before the merge button works. He approves, and Mira merges.

Build it: the record, the sign-offs and the way back (30 min). A one-page REPOSITORY.md:

  • Access: plate-summary-reader, read-only, plate results for three named groups. Proved: a request for another group's plate returns forbidden. Signed by Jonas for the LIMS and the three group leaders for their data.
  • Protection: main protected as listed; direct push refused on 29 September. Signed by the Gitea administrator.
  • Model: the institute's gateway only; sharing disabled; test data is made up. Signed by IT.
  • On failure: LIMS down or token expired, the script stops and writes nothing; users fall back to the exported file, which still works (M6).
  • Undo: a merged change is reversed with a new pull request made by git revert, which adds a commit that cancels an earlier one without rewriting history. The previous version tag stays available. Jonas can disable the API user in one step.

What it taught her: the assistant wrote the LIMS code in minutes. The week went into deciding what it may touch, proving that main refuses a push, and getting an account that can read one thing.

Company: the returns report and the ERP

Structure it. Leonie writes: "The returns report should read returns from the ERP every Monday and feed the quality dashboard. Three of us change the code. No real customer data in any AI chat, no change to main without review, and the report's login reads only what the report needs."

Understand it. She asks the ERP team what the report actually reads: product, reason code, return date and quantity. The export the team has been using also holds customer names and addresses. The ERP team can create a database view, a saved query that shows only chosen columns, and a service account that can read only that view.

The pasted customer export in a free-model chat goes to the data protection officer, who records it and asks what else was pasted. Back to Structure it: "The report never sees a customer name, the assistant never sees real data, and the model is the one the company approved for source code."

Choose tools. OpenCode Desktop with the company's approved model route, configured in opencode.json with sharing off, the same permissions as the lab, and the free models not used for this repository. The repository moves into the company's GitHub organisation, which is on a paid plan, so main can be protected. Instead of Gitea's settings she uses GitHub's branch protection with require a pull request, one approval, and the most recent push must be approved by someone other than the person who pushed it. That last setting closes the Friday gap: nobody can approve their own final change.

Build it. The ERP team creates the view and the service account; the password goes into the company's password vault and into the server's environment, never the repository. The change arrives as a pull request with new spec rules and known-answer tests built from made-up rows.

The pre-push hook's secret scan catches one thing on the first push: a test file where the assistant had copied a sample connection string, the one line that gives a program a database's address and login, with a real-looking server name and password. It was made up, but it looked real enough to stop the push, and she replaces it with an obvious placeholder. Her colleague reviews against the spec and approves.

Sign-offs. The ERP team for the account and the view, the head of quality as the data owner, the data protection officer for the model route and the removal of customer data, and IT for the GitHub protection. The undo is the same as the lab's: a revert through a pull request, and the ERP team can lock the account.

In both places, what made the AI-assisted code safe to run against real systems wasn't a better prompt. It was the assistant's rules in the repository, a second person on every change, and an account that can read one thing.

What goes wrong

The assistant pushes on its own. Symptom: commits you didn't make appear on main, authored by you. Fix: deny git commit and git push in opencode.json, and protect main so a direct push fails anyway.

Secrets in the repository. Symptom: a password in a settings file, or in a test the assistant wrote. Fix: keep secrets in a file Git ignores, commit an example file without values, block the assistant from reading the real one, and scan for secrets before every push. If a real secret was pushed, have it changed that day.

Real data in the chat. Symptom: a real export pasted "so it understands the columns". Fix: made-up rows in tests and in the chat, the approved model only, and the free models not configured for work repositories.

Approving your own change. Symptom: the author merges on a Friday with nobody else looking. Fix: one required approval from someone else, stale approvals dismissed, and the rule applied to administrators.

Review by skimming. Symptom: "looks good" on a diff of 400 lines. Fix: small pull requests, each naming the spec rules it implements. The reviewer checks each block against a rule.

A personal login in the code. Symptom: the Monday run fails because a colleague changed their password or left. Fix: a service account for the job, with a named person responsible in IT and read-only access to exactly what the code needs.

Protection nobody tested. Symptom: the settings page says main is protected, and a direct push still works for administrators. Fix: try a direct push yourself and keep the refusal in the repository record.

Do it yourself (4 hours, over a week)

  1. Structure it (15 min). Write what the repository does, who changes it, which real system it touches, and what must never happen.
  2. Understand it (45 min). Check who can push to main and how often they do. Run a secret scan over the history. Ask the system's administrator what the code needs to read and what kinds of accounts they can create. Rewrite your statement.
  3. Choose tools (15 min). Decide the approved model route, the permissions, where secrets live and who administers the repository.
  4. Build it: rules and checks (50 min). Write opencode.json, .gitignore, .env.example and the pre-push hook. Commit them through a pull request.
  5. Build it: protection (20 min, with the administrator). Protect main, then try a direct push and keep the refusal.
  6. Build it: the first change (90 min). Branch, spec rules, known-answer tests, Plan, Build, review, push with the hook, pull request, a colleague's review, merge.
  7. Build it: the record (20 min). Access and what you proved, protection, model, failure, undo, sign-offs.

Exit check

What you hand in: one merged pull request in a real team repository, made with OpenCode, plus REPOSITORY.md with:

  • the service account, what it can read, and one denial you proved;
  • the branch protection settings and the refused direct push;
  • the assistant's rules: model route, sharing off, what it may never read or run;
  • what happens on failure and how to undo a merged change;
  • sign-offs from IT, the system's administrator and the repository administrator.

It passes when main rejects a direct push, someone other than the author approved the pull request against named spec rules, the tests and the secret scan ran before the push, and no secret is in the repository.

Rule to remember

The assistant works with your rights, so give it fewer than you have, and let nothing reach main without a second person.

Details for experts

Checked on 23 September 2026. You don't need this to follow the book; it's for setting things up or answering a colleague.

WhatDetailsWhat it means for you
Downloadsopencode.ai/download: macOS (Apple Silicon and Intel), Windows (x64), Linux (.deb and .rpm).Check colleagues' computers are on the list.
Sharing"share" in opencode.json: manual (default, /share creates a public link with the whole conversation), auto or disabled.Set disabled in every work repository.
PermissionsKeys such as read, edit, bash, webfetch and external_directory, each allow, ask or deny, with patterns. The last matching rule wins. Most actions default to allow; external_directory defaults to ask; .env files are unreadable by default.Put the catch-all rule first and the exceptions after it.
Provider keysKeys entered through Connect provider are stored on your computer (~/.local/share/opencode/auth.json). A company gateway can be added as a provider with its own address (baseURL) in opencode.json.The file can name the gateway; the key stays personal.
Gitea protectionDisable push or allow listed users only, required approvals, dismiss stale approvals, block merge on rejected reviews, required status checks (automatic checks that must pass before a merge), protected file patterns (files nobody may change without extra rights).Status checks come with T08-L05.
GitHub protectionRequired approvals; approval of the most recent push by someone other than the pusher; restrictions applied to administrators. Private repositories need GitHub Team or higher.Budget for the paid plan before you rely on it.
gitleaksgitleaks git scans a repository's history; latest release 8.30.1.Run it once over the whole history before you start.
Hooks and revertgit config core.hooksPath .githooks makes Git use committed hooks. git revert -m 1 <merge commit> undoes a merged pull request with a new commit; -m 1 tells Git to go back to main as it was before the merge.Everyone runs the first once; the second is your undo.

Further reading and tools

Next books

Tools

  • OpenCode: Taught. Set-up, opencode.json, permissions, sharing, Plan and Build.
  • Git: Taught. Branches, hooks and revert.
  • Gitea: Taught. Branch protection and pull requests in the lab example.
  • GitHub: Taught. Branch protection and pull requests in the company example.
  • Claude Code: Catalogued. The same approach with its own settings file.