<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alexander Korovin</title>
    <description>The latest articles on DEV Community by Alexander Korovin (@korovinaa97).</description>
    <link>https://dev.to/korovinaa97</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4052749%2F985bcc07-3740-41f7-a511-d0b1e4d83fec.jpg</url>
      <title>DEV Community: Alexander Korovin</title>
      <link>https://dev.to/korovinaa97</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/korovinaa97"/>
    <language>en</language>
    <item>
      <title>What does a green CI check actually prove?</title>
      <dc:creator>Alexander Korovin</dc:creator>
      <pubDate>Thu, 10 Sep 2026 11:25:25 +0000</pubDate>
      <link>https://dev.to/korovinaa97/what-does-a-green-ci-check-actually-prove-hn5</link>
      <guid>https://dev.to/korovinaa97/what-does-a-green-ci-check-actually-prove-hn5</guid>
      <description>&lt;p&gt;When I look at a pull request, I do not really care that a job named &lt;code&gt;test&lt;/code&gt; is green. I care that the right tests ran, on the right commit, under a policy the pull request could not quietly weaken.&lt;/p&gt;

&lt;p&gt;The green badge is still useful. It compresses a lot of work into one bit: pass or fail. Trouble starts when that bit becomes the whole argument for merging.&lt;/p&gt;

&lt;p&gt;GitHub can associate a check run with a commit SHA, and branch protection can require a check from a selected GitHub App. But GitHub also documents an important boundary: required status checks are selected by name and do not take the workflow, matrix, or event type into account. A check name is therefore a useful merge control, not a complete answer to four separate questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which exact candidate produced this result?&lt;/li&gt;
&lt;li&gt;Which workflow and concrete job produced it?&lt;/li&gt;
&lt;li&gt;Did the executed checks cover every file changed by this candidate?&lt;/li&gt;
&lt;li&gt;Which trusted policy decided that the evidence was enough?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can ignore much of this in a small repository with one obvious workflow. It becomes important once a repository has several workflows, path-specific checks, reruns, generated code, or automation that copies CI results into another gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce the missing binding
&lt;/h2&gt;

&lt;p&gt;I wanted to see this failure in a tiny example—not just describe it—without needing credentials or access to a real repository. So I added a synthetic pattern named FFA-001 to Fleet Failure Atlas. It models a deliberately weak gate that accepts a successful receipt by status while forgetting to bind the receipt to the current candidate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 https://github.com/korovin-aa97/fleet-failure-atlas.git
&lt;span class="nb"&gt;cd &lt;/span&gt;fleet-failure-atlas
python3 atlas.py run FFA-001 &lt;span class="nt"&gt;--mode&lt;/span&gt; reproduce
python3 atlas.py run FFA-001 &lt;span class="nt"&gt;--mode&lt;/span&gt; detect
python3 atlas.py run FFA-001 &lt;span class="nt"&gt;--mode&lt;/span&gt; regress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The relevant output is small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reproduce: vulnerable_gate_accepts = true
           receipt_sha = 1111...1111
           candidate_sha = 2222...2222

detect:    head_sha_mismatch
           coverage_not_bound_to_head

regress:   repaired_gate_accepts = false
           fresh_receipt_accepts = true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One easy detail to miss: the command exits successfully because the fixture proved its expected condition. In &lt;code&gt;reproduce&lt;/code&gt; mode, “pass” means the contained failure was reproduced—not that the vulnerable predicate is safe.&lt;/p&gt;

&lt;p&gt;The fixture uses synthetic 40-character identities. It is not evidence that GitHub attached a check run to the wrong commit, and it is not presented as a real incident. It demonstrates a more general engineering error: downstream automation accepted a detached green result without checking its subject and coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the evidence contract needs
&lt;/h2&gt;

&lt;p&gt;A stronger decision has to join several facts instead of looking at a display name and conclusion in isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Subject identity
&lt;/h3&gt;

&lt;p&gt;The evidence must name the exact pull-request head SHA being evaluated. A receipt for yesterday’s commit is irrelevant even when every test in that receipt passed.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Producer identity
&lt;/h3&gt;

&lt;p&gt;The expected GitHub App is useful, but a multi-workflow repository often needs more: workflow path, event type, concrete job, workflow run, and latest attempt. This prevents an identically named job in another workflow from being mistaken for the required producer.&lt;/p&gt;

&lt;p&gt;GitHub itself recommends unique job names across workflows because duplicate names can create ambiguous required-check results.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Changed-surface coverage
&lt;/h3&gt;

&lt;p&gt;“Tests passed” is incomplete when no rule connects changed paths to the tests that should have run. A policy can map surfaces to evidence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[surfaces]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"python"&lt;/span&gt;
&lt;span class="py"&gt;patterns&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"src/**/*.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tests/**/*.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"pyproject.toml"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;checks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The safest default is fail-closed: an unmapped changed file is a finding, not a silent exemption.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Policy provenance
&lt;/h3&gt;

&lt;p&gt;If a pull request can weaken the policy that judges the same pull request, the result is circular. Load the manifest from the base commit, protect the manifest and verifier paths, and keep the workflow that invokes the judge under an independent control.&lt;/p&gt;

&lt;p&gt;Base-held policy answers “what policy applies?” A protected required workflow or independent review answers “who ensures the judge runs?” These are different parts of the boundary.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Freshness and reruns
&lt;/h3&gt;

&lt;p&gt;An older successful attempt should not hide a newer failure or an in-progress rerun. Evaluate the newest unambiguous concrete attempt, validate timestamps, and treat retrieval ambiguity as an invalid evaluation instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn the decision into a receipt
&lt;/h2&gt;

&lt;p&gt;I implemented the same model in CI Evidence Gate, a read-only GitHub Action. Its local demo creates disposable Git repositories and returns three verdicts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 https://github.com/korovin-aa97/ci-evidence-gate.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ci-evidence-gate
&lt;span class="nv"&gt;PYTHONPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;src python3 scripts/run_demo.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;valid        sufficient   findings=none
failed test  insufficient findings=required-check-conclusion
policy edit  invalid      findings=protected-policy-change
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three outcomes intentionally separate two kinds of failure:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sufficient&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The declared evidence exists for the exact subject and covers the changed surface.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;insufficient&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The evaluation is trustworthy, but required evidence is missing, stale, incomplete, or unsuccessful.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;invalid&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The judge cannot trust its policy, inputs, provenance, or retrieval result.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The receipt records the base and head SHA, base-policy digest, changed files, matched surfaces, expected checks, observed producer metadata, findings, and the final verdict. That makes the merge decision inspectable after the green or red UI indicator is gone.&lt;/p&gt;

&lt;p&gt;The production Action requests only &lt;code&gt;contents: read&lt;/code&gt;, &lt;code&gt;checks: read&lt;/code&gt;, and &lt;code&gt;actions: read&lt;/code&gt;. It does not write comments, checks, pull requests, repository files, or artifacts. The local demo uses an in-memory provider; production evaluation queries GitHub rather than accepting candidate-supplied evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this still does not prove
&lt;/h2&gt;

&lt;p&gt;An evidence contract is deliberately narrower than a correctness claim. Even a perfectly bound receipt does not prove that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the tests assert the right behavior;&lt;/li&gt;
&lt;li&gt;the source code is correct;&lt;/li&gt;
&lt;li&gt;an evidence-producing job did meaningful work instead of a no-op;&lt;/li&gt;
&lt;li&gt;a compromised runner or expected GitHub App is trustworthy;&lt;/li&gt;
&lt;li&gt;the repository rules actually prevent bypass;&lt;/li&gt;
&lt;li&gt;a candidate-controlled workflow cannot stop the gate from starting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a small repository with one obvious workflow, native branch protection may already be enough. Adding another gate creates maintenance and availability cost, so the extra machinery should correspond to a real provenance or coverage problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical review checklist
&lt;/h2&gt;

&lt;p&gt;Before introducing a custom gate, I would review the existing CI in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give jobs unique names across workflows.&lt;/li&gt;
&lt;li&gt;Require the expected source App where GitHub supports it.&lt;/li&gt;
&lt;li&gt;Confirm every decision is tied to the exact candidate SHA.&lt;/li&gt;
&lt;li&gt;Decide whether workflow path and event type matter for this repository.&lt;/li&gt;
&lt;li&gt;Map changed surfaces to required checks and fail on unmapped files.&lt;/li&gt;
&lt;li&gt;Load policy from a trusted base and protect the verifier’s own files.&lt;/li&gt;
&lt;li&gt;Evaluate the latest concrete rerun rather than any historical success.&lt;/li&gt;
&lt;li&gt;Fail closed on ambiguous provenance or incomplete API results.&lt;/li&gt;
&lt;li&gt;Pin third-party Actions to full commit SHAs.&lt;/li&gt;
&lt;li&gt;Keep the claim precise: evidence sufficiency is not program correctness.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The question I now use in review is not just “is CI green?” It is “can I explain why this green result belongs to this change?” If the merge is important enough to gate, the evidence behind that verdict should survive the color of the UI.&lt;/p&gt;

&lt;p&gt;Runnable references:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/korovin-aa97/fleet-failure-atlas/blob/main/patterns/001-stale-green-ci.md" rel="noopener noreferrer"&gt;Fleet Failure Atlas — FFA-001 stale green CI evidence&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/korovin-aa97/ci-evidence-gate" rel="noopener noreferrer"&gt;CI Evidence Gate repository and 60-second demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/korovin-aa97/ci-evidence-gate/blob/main/examples/sample-receipt.json" rel="noopener noreferrer"&gt;Synthetic sample receipt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/rest/guides/using-the-rest-api-to-interact-with-checks" rel="noopener noreferrer"&gt;GitHub: using the REST API to interact with checks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/troubleshooting-rules#troubleshooting-required-status-checks" rel="noopener noreferrer"&gt;GitHub: troubleshooting required status checks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/reference/security/secure-use" rel="noopener noreferrer"&gt;GitHub: secure use reference for immutable Action pinning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I maintain both open-source projects linked above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>devops</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I stopped writing bug reports. Now I complain at my screen.</title>
      <dc:creator>Alexander Korovin</dc:creator>
      <pubDate>Thu, 30 Jul 2026 12:35:05 +0000</pubDate>
      <link>https://dev.to/korovinaa97/i-stopped-writing-bug-reports-now-i-complain-at-my-screen-845</link>
      <guid>https://dev.to/korovinaa97/i-stopped-writing-bug-reports-now-i-complain-at-my-screen-845</guid>
      <description>&lt;p&gt;I build a product solo. That also makes me the QA team.&lt;/p&gt;

&lt;p&gt;Finding bugs was never the slow part. The slow part came after. I would reproduce a bug, and then spend the next hour turning it into a ticket: watch the recording again, write down the steps, scroll for the frame where it breaks, copy the error text, and try to match "it froze right here" with the right lines in the logs.&lt;/p&gt;

&lt;p&gt;At some point I noticed a stupid thing. While reproducing a bug, I talk. I say things like "why is this button doing nothing" and "wait, the price is wrong again". Everything a good bug report needs was already in that moment. I just had no way to keep it.&lt;/p&gt;

&lt;p&gt;So I built one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow now
&lt;/h2&gt;

&lt;p&gt;I press record, click through the app, and complain out loud. Then I open Claude Code and run one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/talkthrough:bug recordings/checkout-bug.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind it is &lt;a href="https://github.com/korovin-aa97/talkthrough-mcp" rel="noopener noreferrer"&gt;talkthrough-mcp&lt;/a&gt;, a free open source MCP server (MIT) that runs on my machine. What happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The recording gets indexed locally: a transcript with timestamps, key frames, OCR text from the screen, and the real time of day for each moment when the file metadata allows it.&lt;/li&gt;
&lt;li&gt;Claude does not read the whole video. It queries that index: search the words, pull the exact moment, look at the actual frame.&lt;/li&gt;
&lt;li&gt;It writes one GitHub issue draft: what happened, what I expected, steps to reproduce, severity. Every claim has proof attached: my words from the recording, the timestamp, the error text from the screen, and the frame reference.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a full 78-second run, not edited. A narrated recording goes in, a ready-to-file bug-report.md comes out:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/nHfGfEiVdE8"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The draft is the end of the pipeline on purpose. &lt;code&gt;/talkthrough:bug&lt;/code&gt; never creates or edits an issue on its own. I read the draft, sometimes fix a detail, then file it with &lt;code&gt;gh&lt;/code&gt; or paste it into the tracker. An agent that silently writes into your bug tracker is a bigger problem than one extra step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why everything runs locally
&lt;/h2&gt;

&lt;p&gt;talkthrough never uploads the recording. Whisper runs on CPU, OCR and frame extraction are local, and there is no talkthrough account, no cloud backend, no telemetry, no extra API key. Claude sees only the text and images that the MCP tools return, inside your normal Claude Code session.&lt;/p&gt;

&lt;p&gt;This matters more than it sounds. Recordings are full of things you do not want to send to a random service: customer data on the screen, internal URLs, half-finished features. And meetings are even worse. Which brings me to the part I did not plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  The use case my colleagues found
&lt;/h2&gt;

&lt;p&gt;I shared the tool with colleagues at my day job. They mostly ignored the bug part and pointed it at recordings of real work meetings.&lt;/p&gt;

&lt;p&gt;Since v0.2 talkthrough has local speaker diarization (sherpa-onnx: no torch, no accounts, no GPU). A recorded meeting becomes a transcript where speakers are labeled S1, S2 and so on, and every remark carries the real time of day. So you can ask "what did we decide about the migration" and get an answer with a quote, a speaker and a timestamp, without rewatching anything.&lt;/p&gt;

&lt;p&gt;One real number from their feedback: a 65-minute Teams call with about 16 people took around 15 minutes to process on a busy work laptop, diarization included. Not instant. Still much better than an hour of rewatching.&lt;/p&gt;

&lt;p&gt;Their reports also shaped the tool more than my own plans did. For example, the warning you get when the detected speaker count looks implausible came straight from a real meeting where clustering went wrong. When your only test data is your own voice, real users are a gift.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to check the numbers
&lt;/h2&gt;

&lt;p&gt;I do not like demo magic, so the repo has a fully reproducible example: &lt;a href="https://github.com/korovin-aa97/talkthrough-mcp/tree/main/examples/bug-from-silent-recording" rel="noopener noreferrer"&gt;examples/bug-from-silent-recording&lt;/a&gt;. It is a Playwright-recorded clip with no audio track at all (silent recordings work too, through frames and OCR), plus the unedited agent output. The job id is the sha256 prefix of the file, so you can verify every number on your machine: same file, same id, same keyframes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Local files only. No YouTube, no URL ingest (planned).&lt;/li&gt;
&lt;li&gt;The first run downloads the models, so it is slow once.&lt;/li&gt;
&lt;li&gt;The analysis uses the transcript plus key frames, so it can miss motion between frames. You can still pull an exact full-resolution frame for any timestamp.&lt;/li&gt;
&lt;li&gt;Diarization quality depends on the audio. Very short remarks sometimes get the wrong speaker; passing &lt;code&gt;num_speakers&lt;/code&gt; when you know the headcount helps a lot.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;In Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add korovin-aa97/talkthrough-mcp
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;talkthrough@talkthrough
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a plain stdio MCP server, so it is not tied to Claude: configs for Cursor, Codex CLI and ten more clients are in the repo. Everything is here: &lt;a href="https://github.com/korovin-aa97/talkthrough-mcp" rel="noopener noreferrer"&gt;https://github.com/korovin-aa97/talkthrough-mcp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One design question I keep going back and forth on: should &lt;code&gt;/talkthrough:bug&lt;/code&gt; stay draft-only, or should it also be able to create the GitHub, Linear or Jira issue after an explicit confirmation? I would honestly like to know what you would want.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>ai</category>
      <category>opensource</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
