Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
158642 stories
·
33 followers

Experiments with AI Code Review

1 Share
Part of a series on Wealthfront’s AI Developer Tooling Why Code Review? At Wealthfront, our arc of AI adoption has followed a familiar pattern: from skepticism to experimentation to deep adoption in the code lifecycle. Just a few years ago, in the dark era of “AI coding is just fancy autocomplete,” we heard rumors of... Read more
Read the whole story
alvinashcraft
5 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Building orchat - A Single File .NET Client for OpenRouter

1 Share

Overview

OpenRouter emailed me to say my credits were about to expire. I had bought $10 in March 2024, spent seven pence of it, and forgotten the account existed. Credits older than twelve months expire if the account sees no activity, and running any inference resets the clock.

Easy enough. Send a request, keep the money. Except I could not prove it worked. The dashboard rounds every figure to two decimal places, and a request big enough to keep an account alive costs about $0.0002. Every screen I looked at said $0.00, both before and after.

So I wrote a client. It ended up being more interesting than the problem that prompted it, because two things landed at once: .NET 10 lets you run a .cs file with no project around it, and OpenRouter puts every model worth trying behind one API and one key. Put those together and a usable chat client with model browsing and cost reporting is about 300 lines in a single file you can email to someone.

It is called orchat and the whole thing is on GitHub at solrevdev/solrevdev.orchat.

This post is about that file. I have written about .NET global tools and .NET 10 before. This is the other end of the scale: the smallest thing that is still a real application.

A Single File, No Project 📄

.NET 10 runs a loose .cs file:

dotnet run orchat.cs

No .csproj. No obj folder in sight. No dotnet new. The SDK compiles the file to a cache directory and runs it. Check you are on 10 or later first, because this does not exist before then:

dotnet --version

Top-level statements do the heavy lifting. The file opens with using directives and then just starts doing things:

const string Base = "https://openrouter.ai/api/v1";

var key = Environment.GetEnvironmentVariable("OPENROUTER_API_KEY");
if (string.IsNullOrWhiteSpace(key))
{
    Console.Error.WriteLine("Set OPENROUTER_API_KEY first.");
    return 1;
}

Local functions can follow the statements, and type declarations go at the end. So the whole program reads top to bottom: setup, the main loop, then the functions it calls, then a record at the bottom.

Three things surprised me about how far this scales.

You can add NuGet packages. A directive at the top of the file pulls a package, no project required:

#:package Humanizer@2.14.1
using Humanizer;
Console.WriteLine(TimeSpan.FromMinutes(90).Humanize());   // "1 hour"

I did not use it. Everything orchat needs is in the shared framework, and I wanted the file to stay copy-and-paste portable. But the ceiling is a lot higher than “toy script”.

It can be its own command. Put a shebang on line one, mark it executable, and the source file becomes the program:

#!/usr/bin/env dotnet
chmod +x orchat.cs
./orchat.cs

The compiler ignores that line, so dotnet run, dotnet build and dotnet publish all carry on working.

There is a way out. If it outgrows one file, you are not stuck rewriting the scaffolding by hand:

dotnet project convert orchat.cs --dry-run
dotnet project convert orchat.cs

That last one matters more than it sounds. The usual objection to scripts is that they become load-bearing and then you are trapped. Here the exit is one command and it keeps your code.

What orchat Does 🎯

It is a chat client that keeps the whole thread in memory, and a model browser.

This early run used anthropic/claude-sonnet-4.5, so the cost below reflects that model’s pricing at the time:

> what is the difference between a record and a class in C#?
Records are reference types with value-based equality...
[26 in / 180 out, $0.002784, 2.1s, session $0.0028]

> now show me when that equality actually bites

The second question sees the first exchange. Every turn sends the system prompt plus the entire conversation, so follow-ups work the way you would want. Nothing is trimmed or summarised, which keeps the context honest at the cost of a bill that grows with the thread. The in count on the stats line is that growth, in public.

The commands are deliberately few:

/model <id>              switch model
/models [filter|free]    browse the catalogue
/models use 12           switch to row 12
/key                     usage for this key
/cost                    what this session has spent
/system <text>           replace the system prompt
/reset                   clear the thread, keep the model
/save [file]             write the thread to markdown
/exit

If /save cannot write the file, it reports the error and keeps the session open. A bad path should not end the process and lose the thread you were trying to save.

try
{
    File.WriteAllText(path, sb.ToString());
    Console.WriteLine($"Saved {path}");
}
catch (Exception ex)
{
    Console.Error.WriteLine($"Could not save thread: {ex.Message}");
}

Talking to OpenRouter 🔌

OpenRouter’s API is the OpenAI chat completions shape, so there is nothing exotic to learn. One base URL, one bearer token, and the model as a string in the body:

var model = Environment.GetEnvironmentVariable("OPENROUTER_MODEL")
            ?? "openai/gpt-5.6-luna";

var http = new HttpClient { Timeout = TimeSpan.FromMinutes(10) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", key);
http.DefaultRequestHeaders.Add("HTTP-Referer", "https://solrevdev.com");
http.DefaultRequestHeaders.Add("X-Title", "orchat");

Those last two headers are OpenRouter specific and optional. They attribute the traffic to your app on their rankings page. Worth setting.

The value here is that openai/gpt-5.6-luna, openai/gpt-oss-20b:free and hundreds of others are the same call with a different string. No SDK per vendor, no separate key per vendor, no separate billing relationship per vendor. For trying things out, that is the whole pitch.

Streaming, and Getting the Bill 💸

Two flags in the request body do the interesting work:

var body = new JsonObject
{
    ["model"] = model,
    ["messages"] = messages,
    ["stream"] = true,
    ["usage"] = new JsonObject { ["include"] = true }
};

stream gives you server-sent events. usage.include makes OpenRouter append a usage block to the final event, with the actual cost of that request. Not an estimate from a price card. What you were charged.

Reading it back is a loop over lines. Anything that is not a data: line is noise, and [DONE] ends it:

using var res = await http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
using var stream = await res.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);

while (await reader.ReadLineAsync() is { } chunk)
{
    if (!chunk.StartsWith("data: ")) continue;
    var data = chunk[6..];
    if (data == "[DONE]") break;

    JsonNode? node;
    try { node = JsonNode.Parse(data); }
    catch { continue; }
    if (node is null) continue;

    // Errors after streaming starts arrive as a final SSE event. The HTTP
    // status stays 200 because OpenRouter has already sent the headers.
    if (node["error"] is JsonNode error)
    {
        var code = error["code"]?.ToString();
        var message = error["message"]?.GetValue<string>() ?? "The stream failed.";
        throw new Exception(string.IsNullOrEmpty(code) ? message : $"{code}: {message}");
    }

    var delta = node["choices"]?[0]?["delta"]?["content"]?.GetValue<string>();
    if (!string.IsNullOrEmpty(delta))
    {
        Console.Write(delta);
        sb.Append(delta);
    }

    var usage = node["usage"];
    if (usage is not null)
        cost = usage["cost"]?.GetValue<double>() ?? 0;
}

HttpCompletionOption.ResponseHeadersRead is the line that makes it stream rather than buffer. Without it you wait for the whole response and then print it all at once, which defeats the point.

The first check skips OpenRouter’s comment lines and keep-alives. The parse guard skips a malformed data event. The explicit error check matters because a provider can fail after streaming starts. At that point the HTTP status is already 200, so OpenRouter reports the failure inside the final SSE event instead.

That cost figure is what makes the tool worth keeping. After every turn:

[26 in / 180 out, $0.002784, 2.1s, session $0.0028]

Six decimal places, because two is useless at these amounts. When you are comparing a frontier model against something a tenth of the price, seeing the real number after each answer changes which one you reach for.

Browsing the Catalogue 🔍

GET /api/v1/models is public and needs no key. It returns everything OpenRouter can route to, with pricing, context length and a description. The count changes as models arrive and leave, so orchat reads the live catalogue rather than keeping a fixed list.

/models sorts by output price, cheapest first, and numbers the rows so you can act on them:

  1. cohere/north-mini-code:free                    256k  free
  2. google/gemma-4-26b-a4b-it:free                 262k  free
 ...
 14. openai/gpt-oss-20b:free                        131k  free

Then /models use 14 switches to it. /models claude filters by substring, /models free shows only the zero-priced ones, and /models 14 prints the full detail for one row. The catalogue is fetched once and cached for the session, so browsing costs nothing after the first call.

One trap worth knowing if you write something similar. Pricing comes back as strings, mostly:

"pricing": { "prompt": "0.000003", "completion": "0.000015" }

Mostly. Some entries omit pricing, and some quote it as a number. GetValue<string>() throws on a number rather than converting, so a naive parser takes the whole listing down over one odd row. Check the kind first:

static double Price(JsonNode? n) => n?.GetValueKind() switch
{
    JsonValueKind.String => double.TryParse(
        n.GetValue<string>(),
        NumberStyles.Float,
        CultureInfo.InvariantCulture,
        out var v) ? v : 0,
    JsonValueKind.Number => n.GetValue<double>(),
    _ => 0
};

OpenRouter uses a dot as the decimal separator, so string prices must use the invariant culture. Without it, 0.000003 becomes 3 under de-DE and fails to 0 under fr-FR. The source imports System.Globalization for NumberStyles and CultureInfo. The same kind check applies to context_length, which is missing on a few entries. This is the sort of thing that never shows up against a mock and always shows up against the real catalogue.

Free Models, and Why They Did Not Help 🆓

OpenRouter carries a genuine free tier. Models with a :free suffix cost nothing. They are rate limited, but real. For a chat client that is a gift.

For my actual problem it was useless. A free call spends nothing, so there is no charge, so it may not count as the billable activity that resets credit expiry. If you are keeping an account alive, use a paid model. A request costing a fraction of a penny is the entire point.

The free list also churns hard. Endpoints get delisted and added constantly, so never hard-code a :free id into anything scheduled. Ask the catalogue what is free today.

Where the Usage Actually Lives 🔎

This is the part that cost me the most time, and it is the reason the post exists.

The obvious endpoint is GET /api/v1/key. It returns your usage, and orchat prints it:

Key 'unnamed': $0.023808 used all time, $9.976192 left on this key.
  today $0.023808   this week $0.023808   this month $0.023808
  free tier: false

usage_daily, usage_weekly and usage_monthly are all in that one response, at full precision, which is exactly what the dashboard hides.

The catch: it reports usage for that one key, not for the account. I had generated a fresh key for the tool. It read zero all-time while my account had spent $0.069 since 2024. Nothing I had done in the web chat room appeared, because that was a different key. Those zeros meant nothing at all about whether the account was alive, and I spent a while believing they did.

The account-level answer is a different endpoint:

curl -s -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  https://openrouter.ai/api/v1/credits
{ "data": { "total_credits": 10, "total_usage": 0.069429591 } }

Nine decimal places. That is the number the dashboard rounds to $9.93 and refuses to elaborate on.

The method that finally settled it was a before-and-after delta. Snapshot total_usage, send one small paid request, wait a minute or two for OpenRouter to aggregate, snapshot again:

before   total_usage  0.069429591
after    total_usage  0.092981691

The rise matched the cost the API had reported for the requests, to the sixth decimal. Timer reset, proven, for about two pence.

The aggregation lag is worth flagging. A reading taken immediately after a request still shows zero. It took between one and two minutes to appear. I nearly concluded the whole thing had failed on the strength of a reading taken five seconds too early.

Keeping the Key Out of the Repo 🔐

The app reads OPENROUTER_API_KEY from the environment and nowhere else. No config file, no .env, nothing to accidentally commit.

That leaves the question of where the key lives. On macOS the answer is already installed:

security add-generic-password -a "$USER" -s OPENROUTER_API_KEY -w

Leave the value off the end and it prompts, twice, echoing nothing. The key never touches your shell history or your scrollback. Read it back for exactly one command:

OPENROUTER_API_KEY=$(security find-generic-password -s OPENROUTER_API_KEY -w) dotnet run orchat.cs

Encrypted at rest, no dependency, works from cron and launchd. I now use this for every local tool secret.

I did consider dotnet user-secrets and rejected it. It needs a package and a UserSecretsId, it ties a secret to one project, and Microsoft’s own documentation says it does not encrypt anything and should not be treated as a trusted store. It is a .env file with more steps.

Because the store differs by platform, orchat ships two small launchers rather than making that command a thing you retype. orchat.sh tries the environment, then security, then secret-tool on Linux. orchat.ps1 tries the environment, then PowerShell SecretManagement, then the native stores. Both check the SDK version and say something useful when it is too old.

Publishing a Native Binary 📦

The last trick. A file-based app publishes:

dotnet publish orchat.cs -o ./dist
./dist/orchat

That compiles ahead of time to native code. On my arm64 Mac the result is a 5.4 MB self-contained executable with no runtime to install, and it starts in 117 ms against 685 ms for dotnet run orchat.cs. Same work, including a network call, so that gap is build-and-launch overhead the binary does not carry.

Native compilation is also the reason I care about two warnings that looked cosmetic. The first draft built the request like this:

var messages = new JsonArray { new JsonObject { ["role"] = "system", ["content"] = system } };

That binds to JsonArray.Add<T>, which raises IL2026 and IL3050: not trim safe, not AOT safe. It runs fine under dotnet run. Published as a native binary it would have compiled cleanly and then failed at runtime the first time it sent a message, because the reflection path it needs is not there any more.

The fix is a cast, so it binds to the JsonNode overload instead:

var messages = new JsonArray { (JsonNode)new JsonObject { ["role"] = "system", ["content"] = system } };

If you take one thing from this section: trimming warnings in a file-based app are not noise. They are the difference between a binary that works and one that fails on its first real request.

Try It 🚀

# get it
git clone https://github.com/solrevdev/solrevdev.orchat.git
cd solrevdev.orchat

# store the key once
security add-generic-password -a "$USER" -s OPENROUTER_API_KEY -w

# run it
./orchat.sh

Then:

> /models free
> /models use 14
> explain server-sent events in two sentences
> /model openai/gpt-5.6-luna
> the answer above came from a much smaller model. what did it miss?
> /cost

The model is read fresh on every request, so /model switches mid-thread and the new model inherits the whole conversation. Handing one model another model’s answer and asking what is wrong with it is the most useful thing this tool does, and it took no code at all. It falls out of keeping the thread in a list.

What I Took Away 💡

  1. File-based apps are past the toy stage. Packages, shebangs, native publish, and a one-command exit to a real project. The reasons not to start something as a single file are getting thin.
  2. Ask the API what it charged you. usage.include returns the real cost per request. Printing it after every turn changed which models I reach for more than any benchmark has.
  3. Check which scope an endpoint reports on. /key and /credits both return something called usage. One is per key, one is per account, and reading the wrong one had me convinced a working thing was broken.
  4. Rounding hides the truth in both directions. A dashboard showing $0.00 was not telling me nothing happened. It was telling me it could not be bothered to show four more digits.
  5. Parse the response you get, not the one in the docs. Prices as strings except when they are numbers, missing context lengths, keep-alive lines in the event stream. None of it is documented and none of it survives a mock.
  6. Free models are not free activity. Zero cost means zero charge means no billable event. If something downstream depends on you spending money, spend money.
  7. The keychain was there all along. Two commands, encrypted, no dependency. I had been putting keys in dotfiles for years for no reason.

What Is Next 🔮

  • A /compare command that sends one prompt to several models and prints the answers with their costs side by side.
  • Token counts per model in /cost, so the session summary shows where the money went.
  • Nothing scheduled. OpenRouter emails before credits expire, so the email is the reminder and a cron job would be one more thing to maintain.

The whole thing is one file, no project, no packages, and it fits in a screenful of scrolling. If you have an OpenRouter key and .NET 10, you can have it running in about a minute.

The source is at solrevdev/solrevdev.orchat, MIT licensed. orchat.cs is the entire application, so you can read it in one sitting.

Success! 🎉

Read the whole story
alvinashcraft
6 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

SQL Server Deadlock: Build One With Your Own Hands

1 Share

A SQL Server deadlock occurs when sessions form a cycle of dependencies and none can continue. This example lets you create the cycle, inspect it, and remove it.

Transaction (Process ID 57) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

SQL Server resolves the cycle by choosing one transaction as the victim, rolling it back, and returning error 1205. The surviving transaction can then continue.

The two transactions

Demo compatibility: This demo is intended for SQL Server versions or databases where optimized locking is disabled. On SQL Server 2025 and Azure SQL platforms, optimized locking may prevent this exact pattern from reproducing.

Run this setup in a disposable test database, never production.

-- Run once in a disposable test database.
CREATE TABLE dbo.DeadlockDemoAccounts
(
    AccountID int NOT NULL PRIMARY KEY,
    Balance int NOT NULL
);

CREATE TABLE dbo.DeadlockDemoOrders
(
    OrderID int NOT NULL PRIMARY KEY,
    Status varchar(20) NOT NULL
);

INSERT dbo.DeadlockDemoAccounts (AccountID, Balance)
VALUES (1, 1000);

INSERT dbo.DeadlockDemoOrders (OrderID, Status)
VALUES (1, 'Open');

Open two query windows in the same database. Run each numbered step in order.

-- Step 1, in session 1.
BEGIN TRAN;
UPDATE dbo.DeadlockDemoAccounts
SET Balance = Balance - 100
WHERE AccountID = 1;

-- Step 2, in session 2.
BEGIN TRAN;
UPDATE dbo.DeadlockDemoOrders
SET Status = 'Cancelled'
WHERE OrderID = 1;

-- Step 3, back in session 1. This batch blocks.
UPDATE dbo.DeadlockDemoOrders
SET Status = 'Paid'
WHERE OrderID = 1;
IF XACT_STATE() = 1 COMMIT;
IF XACT_STATE() = -1 ROLLBACK;

-- Step 4, back in session 2. This completes the cycle.
UPDATE dbo.DeadlockDemoAccounts
SET Balance = Balance + 100
WHERE AccountID = 1;
IF XACT_STATE() = 1 COMMIT;
IF XACT_STATE() = -1 ROLLBACK;

One session receives error 1205. The other update resumes and commits. Victim selection depends on deadlock priority and estimated rollback cost, so either session can be selected.

After both sessions have finished, clean up the two demo objects.

DROP TABLE dbo.DeadlockDemoOrders;
DROP TABLE dbo.DeadlockDemoAccounts;

Each UPDATE protects its uncommitted change from conflicting writers. Without optimized locking, this commonly appears as an X key or row lock held until COMMIT or ROLLBACK. With optimized locking, row and page locks can be released earlier, while a transaction ID lock protects the uncommitted change. The logical result is still that another writer cannot modify the same row until the transaction finishes, although the deadlock behavior and graph can differ.

Build the deadlock

Run one step from each session, then request the second row from both. Three pictures cover the whole thing.

Open the interactive version if you would rather click through it yourself and watch the cycle form.

State 1. Each session protects one row. Session 1 has updated Accounts. Session 2 has updated Orders. Both hold a lock, neither is waiting, and nothing is wrong yet.

SQL Server Deadlock: Build One With Your Own Hands deadlock-state-1

State 2. Each session asks for the row the other one is holding. Session 1 requests Orders. Session 2 requests Accounts. Both are now blocked, and each is blocked by the other.

SQL Server Deadlock: Build One With Your Own Hands deadlock-state-2

This is the deadlock. It is a cycle, not a slow query. No amount of waiting fixes it, because the only thing that could release either lock is the transaction that is waiting on the other one.

State 3. The lock monitor finds the cycle and breaks it. SQL Server picks a victim, rolls it back, and returns error 1205. The survivor gets both rows and continues.

SQL Server Deadlock: Build One With Your Own Hands deadlock-state-3

Look closely at the victim. Its first UPDATE had already succeeded. It is undone anyway, because a rollback undoes the whole transaction and not only the statement that was blocked.

Victim selection depends on deadlock priority and estimated rollback cost, so either session can be chosen. Do not write code that assumes it is always the other one.

Change one thing and the cycle cannot form

Both sessions take the rows in the same order. Accounts first, then Orders. Session 2 still gets blocked, but look at the graph.

SQL Server Deadlock: Build One With Your Own Hands deadlock-same-order

One arrow. A cycle needs the arrow to come back, and it never does. Session 1 finishes, releases both rows, and Session 2 carries on.

Blocking is not deadlock. Blocking can end when the blocking transaction commits or rolls back, but it can also persist indefinitely if the blocker does not release the resource. That is the whole difference, and it is the reason a consistent access order is the first fix to reach for.

What just happened

Session 1 protects its Accounts change and requests Orders. Session 2 protects its Orders change and requests Accounts. Each session is waiting for a resource protected by the other.

SQL Server’s lock monitor searches for cycles. The normal detection interval is five seconds, but it can fall as low as 100 milliseconds when deadlocks occur frequently.

When the lock monitor finds the cycle, SQL Server rolls back one transaction. The victim receives error 1205, severity 13. Its rollback releases the conflicting resource and the survivor continues. This differs from ordinary blocking. Blocking can end when the blocking transaction commits or rolls back, with no victim selected.

Why lowering the isolation level does not fix this example

READ UNCOMMITTED reduces shared read locking by allowing dirty reads. READ COMMITTED SNAPSHOT and SNAPSHOT use row versions for qualifying reads. These options can reduce some reader-writer deadlocks, but they do not remove this writer-writer dependency.

Both sessions still need to protect their uncommitted changes. For this pattern, the fix is consistent resource access order, not a lower isolation level.

Why each transaction looks correct alone

Each transaction performs a reasonable operation when tested alone. The defect appears only when they overlap and acquire the same resources in opposite order. This is why a deadlock must be analyzed as a workload interaction, not as a single failed statement.

Use a consistent access order

The fourth diagram above is the whole fix. Within this two-resource model, matching order prevents the cycle. One session may block behind the other, but it can proceed once the first transaction finishes.

Apply the same rule across related stored procedures, triggers, cascading actions, and application code. A convention such as parent before child can help. Confirm the actual resources and access paths in the deadlock graph.

Retry the complete transaction

SQL Server rolls back the victim’s entire transaction, including statements that completed before the deadlock. Handle error 1205 with a bounded retry, backoff, and jitter. Retry the complete transaction and ensure the business operation is safe to repeat.

Read the deadlock graph

On SQL Server and Azure SQL Managed Instance, the built-in system_health Extended Events session starts automatically and records detected deadlocks. Its event files roll over, so it preserves recent history rather than an unlimited archive. Azure SQL Database does not include this built-in session.

SELECT CAST(event_data AS xml) AS deadlock_graph
FROM   sys.fn_xe_file_target_read_file('system_health*.xel', NULL, NULL, NULL)
WHERE  object_name = 'xml_deadlock_report';

Each returned event contains the deadlock XML, including the participating processes, requested and owned resources, execution context, and victim information. On SQL Server 2019 and earlier, reading the files requires VIEW SERVER STATE. On SQL Server 2022 and later, it can require VIEW SERVER PERFORMANCE STATE or VIEW DATABASE PERFORMANCE STATE.

Use the graph to identify the cycle before changing indexes, isolation levels, or transaction code.

What the demo leaves out

  • Rows are shown as simply held or free. Real locking has multiple modes and resource types, plus intent locks and possible escalation. With optimized locking, transaction ID locks can also appear.
  • Detection here is instant. SQL Server normally starts with a five-second detection interval, but can detect much sooner when deadlocks occur frequently.
  • The demo estimates rollback cost from the number of completed steps. SQL Server checks DEADLOCK_PRIORITY first, then estimated rollback cost, and can choose randomly when both are equal.
  • Real deadlocks are not limited to two sessions or to rows. Three or more sessions can form a longer ring, and the resources can be pages, keys or memory grants.

Production checklist

Document a resource order. Apply it across every transaction that touches the same objects.

Keep transactions short. Do not hold database locks while waiting for user input, network calls, or unrelated work.

Catch 1205 and use a bounded retry with backoff and jitter. Retry the complete transaction, log the failure, and stop after a sensible limit.

Verify collection and retention. system_health is active on SQL Server and Azure SQL Managed Instance, but rollover removes older events. Use a dedicated Extended Events session when longer retention is required.

Do not assume a lower isolation level fixes a writer-writer cycle. Row versioning can reduce reader-writer blocking and deadlocks. It also changes read semantics, and it does not remove the writer dependency shown here.

If recurring deadlocks require broader analysis, consider a Comprehensive Database Performance Health Check.

A deadlock is a workload-level concurrency defect. Two transactions can look correct in isolation and still fail when they acquire shared resources in an inconsistent order.

Reference: Pinal Dave (https://blog.sqlauthority.com/), X

First appeared on SQL Server Deadlock: Build One With Your Own Hands

Read the whole story
alvinashcraft
7 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Daily Reading List – August 3, 2026 (#837)

1 Share

I’m back today with a big reading list. The short weekend in Seattle was awesome, and I got to see some people I’ve missed since moving away four years ago. I also missed Taco Time, and had to stop there. No apologies.

[blog] Batten Down Your Packages: Mitigation Guidance for Supply Chain Compromise. Learn more about supply chain compromises, and how to protect yourself against this ever-present risk.

[blog] Modern Software Registries are a Trust Service. Related to above. Identity and provenance play a huge part in establishing (and maintaining) trust.

[blog] What happens to a lawyer’s business model when AI makes him 5x faster. There’s a giant set of new “builders” out there. How much will they build? Will they build regularly? Do they have loyalties to a specific stack? I’m not sure anyone knows, but don’t ignore this constituency.

[article] The Economic Benefit of Refactoring. If you have less code, you spend less money on tokens when your AI tool reads it. This is an example of refactoring some bloated AI-generated code and seeing the benefits.

[blog] the one line 95% of agent skills are missing. We’re skipping the instruction that tells the AI tool when an agent should load them.

[blog] From ‘Write a Python Script’ to Multi-Agent Mastery: My Journey to Becoming an AI Builder. We can build in areas outside our core knowledge areas. Amanda might not have been an audio/visual app developer, but that didn’t stop her.

[article] Research: How AI Agents Broaden the Scope of Knowledge Work. It’s not just about doing the current work better. It’s about different work.

[blog] Behind the scenes: How we build, test, and scale Google Agent Skills. How do you create skills at scale? I’m very proud of my team’s work here, and the engineering discipline they put into the effort.

[blog] How Agentic Coding Is Reshaping the Software Development Lifecycle. Very interesting lens on the new SDLC. Are you building for the always-on PM? Software factories that store, build, and secure code differently?

[blog] Do more with less: How GKE can reduce your cost per agent by 75%. From 61 OpenClaw agents on a vanilla Kubernetes node to 274 with a cost optimized, high-density configuration. Pretty good!

[blog] Giving and taking credit in big tech companies. Do you feel a little weird loudly taking credit for something? Probably, unless you’re a narcissistic psycho. But remember that you need to both take and give credit generously in a corporate environment.

[blog] The borderless Lakehouse: Bring AWS, Databricks and Snowflake data to your AI agents. Looks legit. Understand and query data regardless of its physical location.

[blog] Stronger with every update: How we’re making Chrome and the web safer in the AI Era. Excellent post that offers lessons for anyone trying to improve product quality.

[blog] Who’s Writing Open Source Code? Very interesting analysis. Are AI robots writing all the committed open source code now? Not remotely the case (yet).

[blog] Adapting open source practices to an AI-first world: A retrospective on 2025. Related to the previous one, thousands of Googlers are actively contributing to open source projects.

[blog] TypeScript Just Got 10x Faster by Not Being TypeScript. Go was a smart choice by this team. Performance is great, and Go suited the compiler’s needs.

Want to get this update sent to you every day? Subscribe to my RSS feed or subscribe via email below:



Read the whole story
alvinashcraft
7 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

1.0.78

1 Share

2026-08-03

  • Timeline headers show how long each tool call took, right-aligned and ticking live while it runs (for calls of at least 5 seconds). On by default — disable with /settings showToolDurations.
  • First-party plugins automatically update to the latest version at session start
  • Add the experimental /new-worktree command to create a new worktree and start a new conversation in it
  • Copilot login now defaults to the browser flow for local desktop subprocesses without a TTY, including IDE integrations, while remote and headless environments continue using device code
  • Interactive shell shortcut now launches on Enter and shows an inline hint when "$" is armed
  • Extension slash commands run their handler exactly once per invocation when several extensions are loaded
  • Inline images no longer render with their first row repeated down the whole picture after the timeline scrolls
  • A run whose prompt is piped over stdin now treats its sessionEnd hook the same way -p does: the hook fires once per completed agent turn with reason complete (or error if the turn failed), instead of once at shutdown with user_exit. As with -p, a piped run that exits before completing a turn fires no sessionEnd hook
  • Split-view sidebar: the red close confirmation now reads x again to close (or x again to exit CLI on the last session) instead of x close, so a second press is clearly what closes
  • Expose token usage in ACP prompt results and live usage_update notifications
  • Added a forceRemoteSettingsRefresh managed setting that requires a fresh managed-settings fetch on startup
  • Disabling the sandbox from a bypass prompt applies only to that session; new sessions start sandboxed again
  • Managed settings now fall back to the persistent cache whenever a server-managed settings fetch fails for any reason (network error, a non-success HTTP status, or a malformed/unparseable response), and fail open — starting without the unconfirmed server restriction rather than the prior fail-closed behavior — when no usable cached policy is available
  • When the sandbox blocks a shell command and bypass is allowed, CLI offers to re-run it outside the sandbox without asking the model
  • /rewind no longer requires git and restores only the files Copilot changed, skipping any file whose contents no longer match what Copilot last wrote, with a conversation-only or conversation + files choice
  • Add /permissions to switch between approval modes.
  • ACP mode supports closing sessions with the closeSession request.
  • Ctrl+Q now enqueues the highlighted mid-text skill completion instead of the partial token
  • Switching sessions no longer restarts MCP servers or rebuilds hook state, so a turn running in another session is never halted with a stale-hook error
  • Refresh deferred MCP tools after OAuth authentication
  • New sandbox setting allowDevToolCaches (on by default): grants sandboxed builds access to toolchain caches, registries, and installs so builds work without extra setup. Set false to opt out.
  • Honor explicit GitHub MCP toolset/tool config: keep gh-overlap tools and stop steering to the gh CLI when you opt in
  • Warn on startup about unknown top-level keys in user settings.json (e.g. a misspelled setting) instead of silently ignoring them
  • Shell completion for --model now suggests auto and supported model names
  • Render long session transcripts progressively to keep scrolling responsive
  • Resuming a long session is dramatically faster and far lighter on memory, because its history is now read once at startup (in parallel, across CPU cores) instead of being re-read in full for every check the CLI runs before it can paint. In our benchmark a 230MB, 74k-event transcript came back in well under a second instead of about ten, at roughly a quarter of the peak memory; the exact gain depends on your machine's core count and disk
  • The /allow-all auto safety-judge model is no longer user-configurable; the judge model is now selected automatically.
Read the whole story
alvinashcraft
7 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

An AI-Supervised Remote Exam Went So Badly That 58,000 Students Must Retake It

1 Share
An anonymous reader quotes a report from Ars Technica: Earlier this summer, nearly 160,000 applicants took the entrance exam for UNAM, Mexico's largest university. For the first time, they did it completely remotely, using a "lockdown" browser and AI-powered webcam proctoring software, over several weeks from late May through early June. It was a disaster. When exam results came in, they bore little resemblance to past results, especially at the top. Between 2021 and 2025, 3.5 percent of test takers scored 100 or more on the 120-question UNAM test. This year, 16.3 percent did so. The story was even worse at the highest of the high end. Between 2021 and 2025, 0.9 percent of test takers scored 110 or more; this year, 5.5 percent did so. The surge in top scores led to accusations of widespread cheating, and UNAM appointed a commission of experts to investigate the situation. The group was given the unwieldy name "la Comision Tecnica de Personas Expertas para la Revision del Proceso de Seleccion de Ingreso a Licenciatura para el Circlo Escolar 2026-2027/1," and it has just submitted its recommendations. The commission believes that the best path forward, given all the concerns, is to administer a "control exam" -- that is, applicants will have to sit for another test, and they will do so in person. This control exam will apply not only to those who secured a spot at UNAM based on this year's test but also to everyone who would have been admitted based on minimum successful scores in their program of study since 2021. About 58,000 people could be affected, and places at UNAM will now depend on the results of the new test. (Details on the control exam should appear soon; classes are currently scheduled to begin on August 10, so everything will have to move quickly unless the school decides to delay classes.) According to Gaceta UNAM, the school's official news publication, the university rector has apologized to honest applicants, since they will now have to prepare for and take the test again despite doing nothing wrong. Still, the control exam is "necessary to give certainty and guarantee equity in access," the rector added.

Read more of this story at Slashdot.

Read the whole story
alvinashcraft
2 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories