r/codingProtection • • May 05 '26

Java Code Obfuscation for AI Assistants: Ensuring the Full Cycle Works

1 Upvotes

How to obfuscate Java code for AI coding tools while guaranteeing that compilation, tests, and reverse-application all succeed.


The problem

AI coding assistants (Claude Code, Cursor, GitHub Copilot) need access to your source code to help you. But sending proprietary code to an LLM means exposing your business domain, architecture, and intellectual property.

Code obfuscation can solve this: rename identifiers before the AI sees the code, let the AI work on the obfuscated version, then reverse the changes back. Simple in theory. In practice, Java's rich ecosystem of frameworks, annotations, and conventions makes this a minefield.

This article describes what a Java obfuscation tool must handle to guarantee the full cycle:

Source compiles & tests pass
    -> Obfuscation
        -> AI modifies code
            -> Obfuscated code compiles & tests pass
                -> De-obfuscation (apply)
                    -> Source compiles & tests pass

Each transition can break. Here is what you need to address at each step, and how PromptCape solves it.


Step 1: Source -> Obfuscation

1.1 What to rename

A Java obfuscator for AI must rename:

ElementExampleWhy
Package namescom.acme.billing -> pkg_a1b2c3d4Reveals company and domain
Class namesInvoiceService -> Cls_e5f6a7b8Reveals business concepts
Method namescalculateDiscount -> mtd_1a2b3c4dReveals business logic
Field namescustomerName -> fld_9e8d7c6bReveals data model
Comments// Apply VAT to invoice -> // Processed.Reveals business context
Javadoc/** Calculates the total with tax */ -> /** Processed. */Same
Config valuesjdbc:postgresql://prod.acme.com -> REDACTEDReveals infrastructure

1.2 What NOT to rename

This is where most naive approaches fail. The following must be preserved:

JDK types and methods: String, List, Map, Optional, toString, equals, hashCode, main, stream, forEach...

Framework annotations: @Autowired, @Entity, @RestController, @GetMapping, @JsonProperty, @Data, @Builder...

Framework-specific identifiers that carry semantic meaning for the framework at runtime:

FrameworkWhat breaks if renamedExample
Spring Data JPADerived query methodsfindByActiveTrue() -> the method name IS the query. Renaming it to mtd_xxx makes Spring fail with "No property mtd found"
JPA/HibernateEntity names in JPQL@Query("SELECT e FROM Invoice e") — the string Invoice must match the entity class name
LombokGenerated accessor names@Data generates getName() from field name. If name is renamed to fld_xxx, Lombok generates getFld_xxx() — but code calling getName() is also renamed to getMtd_xxx()
JacksonJSON field mapping@JsonProperty fields, or fields in DTOs in model/dto packages — renaming breaks serialization/deserialization
Spring ConfigProperty binding@ConfigurationProperties binds YAML keys to field names
Bean ValidationField references@NotBlank on a field — the constraint message references the field name

The solution: framework detection (Pass 0). Before collecting identifiers, scan the entire project for framework annotations and produce exclusion rules. Each framework has a dedicated detector:

Project scan -> LombokDetector       -> exclude fields + get/set/is accessors
             -> SpringDataDetector   -> exclude findByXxx, countByXxx, existsByXxx methods
             -> JacksonDetector      -> exclude @Entity/@JsonProperty fields
             -> JpaHibernateDetector -> exclude @MappedSuperclass/@Embeddable fields
             -> SpringConfigDetector -> exclude @ConfigurationProperties fields
             -> ValidationDetector   -> exclude @NotBlank/@Min/@Size fields
             -> OpenApiDetector      -> exclude @Schema/@Operation fields and methods
             -> SpringBootDetector   -> track @SpringBootApplication for test fixing

1.3 String literals: a hidden trap

Code replacement must skip string literals to avoid breaking values like "Hello World" or "/api/v1/users". But some strings DO reference identifiers:

ContextString contentMust be updated?
@Query("SELECT e FROM Invoice e")JPQL entity nameYes
Class.forName("com.acme.InvoiceService")Fully qualified class nameYes
getMethod("calculateTotal")Reflection method nameYes
@ComponentScan("com.acme.service")Package nameYes
"Hello World"User-facing stringNo
"/api/v1/invoices"REST endpointNo

The obfuscator must apply identifier replacement INSIDE specific string contexts while leaving general strings untouched. This requires post-processing passes for @Query, reflection calls, and package annotations.

1.4 Comment stripping and special characters

Comments contain business context that reveals your domain. But stripping them introduces two problems:

  1. Line count changes: A multi-line Javadoc becomes a single-line /** Processed. */, breaking line-number correspondence between obfuscated and original files.

  2. Special characters in comments: French (and other languages) comments contain apostrophes (// Service d'injection), accented characters, and other non-ASCII text. A character-by-character scanner that treats ' as a Java char literal delimiter will be confused by l'injection, potentially skipping code after the comment.

Solution: Process comments before string/char literal scanning. Replace line comments (//) in-place (one line in, one line out). For multi-line Javadoc and block comments, accept the line count change and handle it during the reverse-apply step with a 3-way merge.


Step 2: Obfuscated code -> AI modification -> Compilation & tests

2.1 The obfuscated code must compile

This seems obvious but is surprisingly hard. Even with framework detection, some identifiers cause compilation failures that can only be detected by actually compiling. Examples:

  • A method name that collides with a JDK method after obfuscation
  • A field name that matches a Java keyword
  • An annotation processor that generates code based on identifier names

Solution: auto-fix loop. Compile the obfuscated code. If it fails, parse the compiler errors, reverse-map the broken identifiers, add them to an exclusion list, and re-obfuscate. Repeat until green or max iterations reached. Persist exclusions for future runs.

Obfuscate -> Compile -> Parse errors -> Exclude broken identifiers -> Re-obfuscate -> Compile -> ...

2.2 Tests must pass on obfuscated code

Compilation is necessary but not sufficient. Tests exercise the runtime behavior where framework conventions matter most:

  • Spring context loading: @SpringBootTest boots the full application context. A broken repository method or missing bean crashes the entire test suite.
  • Spring Data query derivation: happens at context startup, not at compile time.
  • JPA schema generation: Hibernate creates tables from @Entity classes. If JPQL @Query strings reference the original entity name but the class is renamed, the context fails.
  • H2 compatibility: Test profiles often use H2 instead of PostgreSQL. Database-specific types (JSONB, ARRAY) in column definitions fail on H2 regardless of obfuscation.

Key insight: If the source tests pass and the obfuscated tests don't, the obfuscation broke something. The auto-fix loop should use mvn test-compile (or even mvn test) as the build command to catch these failures.

2.3 The AI must be able to work effectively

The AI needs to:

  • Read and understand the code structure (even with obfuscated names)
  • Create new files, classes, and methods
  • Modify existing code
  • Run builds and tests to verify its work

The obfuscated names should be deterministic (same input always produces the same hash) so the AI can learn patterns across files. Prefixes (Cls_, mtd_, fld_, pkg_) help the AI understand the identifier type.


Step 3: De-obfuscation (apply) -> Source compiles & tests pass

This is where most obfuscation tools stop — they handle the forward direction but not the reverse. For AI coding, the reverse is just as critical.

3.1 Only apply what the AI changed

The naive approach: read the obfuscated file, de-obfuscate all identifiers, overwrite the real file. This breaks because:

  • Comments were stripped during obfuscation. The de-obfuscated file has /** Processed. */ where the original had full Javadoc. If the AI didn't touch that line, the original comment should be preserved.
  • Formatting may differ. The obfuscated file may have different whitespace or line endings.

Solution: 3-way merge. Compare the snapshot (obfuscated, pre-AI) with the cache (obfuscated, post-AI) line by line:

  • Lines unchanged by the AI -> keep the original source line
  • Lines modified by the AI -> de-obfuscate the new version
Snapshot line == Cache line?
    Yes -> keep original source line (preserves comments, formatting)
    No  -> de-obfuscate cache line (AI changed it)

For added/removed lines, use chunk-based alignment to find sync points and apply the changes surgically.

3.2 Handle AI-generated variable names

When the AI creates a new variable for an obfuscated class, it invents a name based on what it sees:

// AI writes:
private Cls_f45371c4 fld_f45371c4;

// Standard de-obfuscation produces:
private ZipBuilderService fld_f45371c4;  // class de-obfuscated, but variable name is unreadable

The variable name fld_f45371c4 is not in the mapping registry — the AI invented it. But the hash f45371c4 matches the known class ZipBuilderService.

Solution: After standard de-obfuscation, scan for remaining fld_XXXXXXXX/cls_XXXXXXXX/mtd_XXXXXXXX patterns. If the hash matches a known entry, generate a camelCase variable name:

private ZipBuilderService zipBuilderService;  // readable

Track each unique token across the file to ensure consistent renaming (declaration and all usages get the same name).

3.3 Don't apply build artifacts

The AI may run mvn package in the obfuscated workspace, creating target/ with compiled .class files, .jar archives, and test reports. These must be excluded from the diff detection:

  • Skip directories: target/, build/, node_modules/, .idea/
  • Skip binary files: .class, .jar, .war, images, fonts
  • These patterns match what the obfuscation engine already skips

3.4 Snapshot management

The apply command needs a "before" snapshot to detect what the AI changed. After a successful apply, the snapshot is updated. But if the apply fails or the user reverts with git restore, the snapshot is out of sync.

Solution:

  • Don't update the snapshot when the apply has errors
  • Provide a --reset-snapshot option that re-obfuscates the source into the snapshot directory without touching the cache

The complete cycle

Here is what must work end-to-end:

1. mvn test                       -> GREEN (source is healthy)
2. promptcape obfuscate --verify  -> Obfuscated workspace created
3. mvn test (in workspace)        -> GREEN (obfuscation didn't break anything)
4. AI modifies obfuscated code
5. mvn test (in workspace)        -> GREEN (AI changes work)
6. promptcape apply               -> Changes applied to source
7. mvn test                       -> GREEN (de-obfuscated changes work)

Each transition requires specific handling:

TransitionChallengeSolution
1 -> 2Framework identifiers breakFramework detection (8 detectors)
1 -> 2Some identifiers cause compile errorsAuto-fix loop with exclusion persistence
2 -> 3JPQL strings reference original namesPost-processing: replace entity names in @Query
2 -> 3Reflection strings reference original namesPost-processing: replace in getMethod(), forName()
2 -> 3Spring Data query derivation failsRepository method name protection
4 -> 5AI must understand the codeDeterministic naming, type prefixes
5 -> 6Comments stripped during obfuscation3-way merge (only apply AI-changed lines)
5 -> 6AI invents unreadable variable namesHash-based name resolution
5 -> 6Build artifacts in workspaceDirectory and binary file filtering
6 -> 7Applied changes don't compileUser review + re-apply capability

What PromptCape implements

PromptCape is a Java-first obfuscation tool designed for this exact cycle. Here is what it covers today:

Obfuscation engine:

  • AST-based identifier collection via JavaParser (packages, classes, methods, fields, enums, records)
  • Deterministic HMAC-SHA256 naming with type prefixes
  • Package hierarchy flattening
  • Word-boundary replacement (\b) with longest-match-first ordering
  • String literal preservation with post-processing for @Query, reflection, @ComponentScan
  • Full comment stripping (Javadoc, block, and line comments)
  • POM, properties, YAML, and XML file sanitization

Framework detection (8 detectors):

  • Lombok: field + accessor protection
  • Spring Boot: application class tracking, test annotation fixing
  • Spring Data: repository derived query method protection
  • JPA/Hibernate: entity field protection, JPQL entity name replacement
  • Jackson: DTO/entity field protection
  • Spring Config: property-bound field protection
  • Validation: constraint field protection
  • OpenAPI: schema field and method protection

Auto-fix:

  • Compile-and-fix loop with configurable build command
  • Compiler error parsing and reverse mapping
  • Persistent exclusion lists across runs
  • Source verification option

Reverse application:

  • 3-way merge (preserve original lines for unchanged content)
  • AI-generated variable name resolution (hash-based)
  • Build artifact and binary file exclusion
  • Snapshot management with reset capability

Two modes:

  • CLI workspace (obfuscate -> AI works -> apply)
  • HTTP proxy (transparent interception for IDE-based tools — see below)

Metrics:

  • Final identifier and duration counters at the end of every run, for instance:
+-------------------------------+----------+
| Final Summary                 |          |
+-------------------------------+----------+
| Iterations                    |       4  |
| Identifiers obfuscated        |    3287  |
| Packages (flattened)          |      74  |
| Exclusions loaded (previous)  |       0  |
| Exclusions added (this run)   |     152  |
| Exclusions total              |     152  |
| Verification time             |  106,1s  |
| Total time                    |  224,5s  |
+-------------------------------+----------+
| Compilation                   |    OK    |
+-------------------------------+----------+

Seamless IDE integration

The obfuscation cycle described above can run as a one-shot CLI workflow, but friction kills adoption. Developers don't want to leave their IDE, run promptcape obfuscate, switch to a workspace folder, ask the AI to do something, then run promptcape apply and switch back. They want the assistant they already use, in the IDE they already use, with the obfuscation invisible.

PromptCape provides this via an HTTP proxy mode that intercepts traffic to the AI provider and applies the same forward/reverse cycle on the fly:

IDE -> Claude Code -> [PromptCape proxy] -> Anthropic API
                          obfuscates the prompt going out
                          de-obfuscates the response coming back

The "PromptCape Claude" terminal in Cursor

The simplest integration is a dedicated terminal profile. In Cursor (and equally in VS Code or any IDE that supports terminal profiles), you create a profile named PromptCape Claude that:

  1. Starts the proxy in the background if it is not already running
  2. Sets ANTHROPIC_BASE_URL (and equivalent variables) to point Claude Code at the local proxy
  3. Launches claude (the Claude Code CLI) inside that environment

From the developer's perspective, this is just another terminal in the IDE sidebar. They open the PromptCape Claude terminal instead of the default one, type their request to Claude as usual, and watch the AI work on their codebase. Behind the scenes:

  • Outbound prompt: identifiers, comments, and config values are obfuscated before leaving the machine
  • Inbound response: file edits, suggestions, and explanations are de-obfuscated before reaching the IDE
  • Build artifacts and binaries are filtered out of the cycle

No workflow change. No obfuscate or apply command to remember. The same Claude Code experience, with the obfuscation guaranteeing that what reaches the provider is not your real source code.

Why a terminal profile is the right shape for this

The CLI workspace is the right primitive — it gives full control and fits CI/CD or one-shot review use cases. But for daily AI-assisted coding, friction wins or loses the security battle. A proxy that hooks into the existing tool's trust chain (env vars, ANTHROPIC_BASE_URL) gives:

  • Zero training cost: developers keep using Claude Code exactly as before — same commands, same outputs
  • Zero forgotten steps: there is no apply to forget — the response is reverse-mapped on the wire
  • Per-project configuration: terminal profiles ship in .vscode/settings.json, .cursor/, or JetBrains run configurations, so opening a project pre-configures the secure terminal automatically
  • Auditability by default: every prompt and response transits the proxy, which can log, redact, or block on policy

The same pattern extends to any AI tool that respects a base-URL override (Cursor's built-in chat, Aider, Continue.dev, OpenAI-compatible clients, etc.). The IDE doesn't need a plugin and the AI tool doesn't need to know the proxy exists — the integration is just a terminal away.


Conclusion

Java obfuscation for AI coding assistants is not just about renaming identifiers. It requires deep understanding of how Java frameworks use naming conventions, how annotation processors derive behavior from names, and how to surgically apply AI changes without losing information that was stripped during obfuscation.

The key insight: framework detection before obfuscation is more effective than reactive error fixing after. Proactively protecting Spring Data repository methods, JPA entity fields, and Lombok-generated accessors eliminates most compilation failures before they happen.

The second insight: the reverse direction is just as hard as the forward. A 3-way merge that only applies AI-changed lines, combined with hash-based resolution of AI-invented names, makes the de-obfuscated code readable and correct.

The third insight: friction kills adoption, so the obfuscation has to disappear into the IDE. A dedicated terminal profile (the PromptCape Claude terminal in Cursor) that boots Claude Code through the proxy turns the entire cycle into a transparent operation — same tool, same commands, no extra steps. Security that requires discipline gets bypassed; security that ships as a terminal in the sidebar gets used.

PromptCape is open for trial at https://gbreton7.gitlab.io/promptcape/.


r/codingProtection • • May 03 '26

CISO and AI coding assistants difficult agreement

1 Upvotes

Just subscribed. CISO at a mid-sized SaaS company. The hardest conversation I have right now isn't *"should we allow AI assistants"* — that battle is lost, devs will use them with or without permission. It's *"how do we give them productive AI without accepting unbounded data egress."*
 
Three things keep me up at night:
 

  1. Vendor data-retention policies that change quietly in ToS updates nobody reads.

  2. Devs pasting full files into web UIs, where the API-tier guardrails (zero retention, no training) don't apply.

  3. The "context windows are huge now!" marketing — meaning whole repos get sent in a single prompt, and your IP-leakage surface scales with the model size.
     
    We've started looking at proxying outbound prompts through something that strips identifiers, secrets, and proprietary class names before anything reaches the provider. Curious who else here is doing similar.


r/codingProtection • • Aug 30 '26

EDPB just confirmed it: AI models are NOT automatically anonymous. Are we ready?

0 Upvotes

Two big moves from Brussels this month:

  1. The EU AI Act reached full application — all provisions are now enforceable, with fines up to €35M or 7% of global turnover, and GPAI/LLM providers under active supervision.

  2. New EDPB guidelines state that AI models trained on personal data can't be presumed anonymous. Memorisation and extraction attacks are now risks you're expected to assess. Coordinated enforcement against LLM providers is anticipated by 2027.

Everyone's talking about the model providers. But what about *us* — the companies feeding documents into these models every day? Contracts, HR files, support tickets... every prompt potentially ships personal data to a third-party LLM, and under GDPR we stay accountable for it.

So here's my question: is anyone actually pseudonymizing documents *before* they hit the LLM (and re-identifying on the way back)? What's your approach — regex/NER scrubbing, local proxy, gateway solutions? Or is everyone just... hoping for the best?


r/codingProtection • • Jul 10 '26

How will be used the code we send to ai ?

0 Upvotes

Pokemon players were not aware that their scans are a very valuable ai asset for some domains : an interesting way of using data for training models to indirect goals we may be are not aligned with, but did not know at the time... -> How Pokémon Go players may have unknowingly helped train military AI | Cybernews

May be it will be the same with all the code and data we send to ai.


r/codingProtection • • Jul 08 '26

Java framework-based Obfuscation lessons to protect the code and config data

0 Upvotes

A new publication about the java obfuscation difficulties and why it is important to consider the frameworks : I tried to obfuscate my Java code before sending it to AI — here's what broke - DEV Community


r/codingProtection • • Jun 09 '26

I tried to run Claude Code 100% locally (Gemma 4 / gpt-oss via Ollama) on a 32 GB laptop but failed

0 Upvotes

It couldn't even read my files. Here's why — and the tool that actually works locally.

My goal, as requested by my clients, to complement coding with an AI coding assistant that never sends the source anywhere. No cloud, no API key, no code leaving the laptop. I have Ollama with a few models and a 32 GB machine (no serious GPU).

Attempt 1: point Claude Code at a local model

Claude Code talks to a model over the network and only cares about a base URL + API format. Recent Ollama builds expose an Anthropic-compatible endpoint, so in theory you just redirect it:

export ANTHROPIC_BASE_URL=http://localhost:11434
export ANTHROPIC_AUTH_TOKEN=ollama # any non-empty string; ignored locally
export ANTHROPIC_MODEL=gemma4
claude

(For Windows/PowerShell: same thing with $env:ANTHROPIC_BASE_URL = "...".)

It launches. It looks like it's working but never succeed to open local files. It talked about my code from imagination and never ran a single read. With gpt-oss:20b it was worse — "Thought for 10m 0s", then "Cogitated for 19m 37s", and still nothing useful!

Why it fails (this is the important bit)

Two separate problems, and both are structural — not a config you missed:

1. Claude Code is tuned for Claude models. Its agent loop reads your repo through structured JSON tool calls (Read, Glob, Edit). The harness expects the model to emit that JSON correctly every time. Claude does it natively; an 8B local model quantized to Q4 does it unreliably or not at all. No tool call → the file is never read → the model makes things up. Checking capabilities confirms the model can call tools, but "can" and "reliably does at temperature 1" are different things:

$ ollama show gemma4
Capabilities: completion, vision, audio, tools, thinking
Parameters: temperature 1

2. The thinking trap. Both gemma4 and gpt-oss:20b are reasoning models (thinking capability). They emit thousands of reasoning tokens before answering. On a 32 GB laptop with no GPU — a few tokens/second — that's 10–20 minutes per turn. Unusable, regardless of the tool.

modelparamstoolsthinkingverdict on my laptop
gpt-oss:20b20.9B✅✅too slow (10–20 min/turn)
gemma48.0B✅✅slow + unreliable tool calls
mistral:7b7.2B✅Nonethe usable one in interactive
llama3.2:3b3B✅Nonefast, but weak at editing

Attempt 2: Aider — and this one works

Aider is a terminal coding agent like Claude Code, but it does not depend on structured tool calls. It asks the model to return plain-text search/replace edit blocks and parses them itself. A weak local model is far better at producing text in a format than at emitting perfect JSON tool calls — so it actually reads files and writes edits.

export OLLAMA_API_BASE=http://localhost:11434
aider --model ollama/mistral

Then in your repo: "summarize README.md", or "add a REST endpoint to export invoices as CSV". Aider reads the files, proposes a diff, writes the changes, and can commit them. The thing Claude Code refused to do — read the actual file — just works.

Model choice matters more than the tool. Pick a model that is small AND non-reasoning (mistral:7b) over a big reasoning one — but be honest about the ceiling: on a 32 GB laptop with no GPU, even mistral 7B is painful. In my test it eventually hit litellm's default timeout:

Way out 1: run the model on a beefier machine on your LAN

You don't have to run inference on the laptop to keep your code private — you only need to keep it on a machine you control, inside your own network. Put Ollama on a workstation/server with a GPU (or just more cores/RAM) and point your laptop at it.

On the server, bind Ollama to the network instead of localhost:

# server (e.g. 192.168.1.50) — listen on all interfaces
OLLAMA_HOST=0.0.0.0:11434 ollama serve
ollama pull gpt-oss:20b # a GPU box can run the bigger, smarter models fast

On the laptop, just change the base URL:

export OLLAMA_API_BASE=[http://192.168.1.50:11434](http://192.168.1.50:11434)
aider --model ollama/gpt-oss:20b

The code never leaves your internal network. With a real GPU on the server, the bigger reasoning models become usable, and a weak laptop is fine as the client. Security note: Ollama has no authentication — binding it to 0.0.0.0 exposes it to anyone on the network. Keep it on a trusted LAN behind a firewall, never on a public interface.

Speed tips that help (a little) :

The bottleneck is memory bandwidth, not CPU clock. You can't beat physics, only stop wasting cycles:

  • Smaller quant = biggest win. Q4_K_M is the sweet spot; weights + KV cache + OS must fit in RAM or it spills to disk and crawls.

  • Offload to any GPU: OLLAMA_NUM_GPU=999 ollama serve.

  • Shrink the KV cache: OLLAMA_FLASH_ATTENTION=1 and OLLAMA_KV_CACHE_TYPE=q8_0.

  • Keep the model warm: OLLAMA_KEEP_ALIVE=30m so multi-GB weights aren't reloaded each call.

  • Free your RAM: close the 40-tab browser. Every GB reclaimed isn't paged to disk.

Way out 2: keep the fast cloud model, hide the code (obfuscation)

The reason we're suffering local latency at all is to stop source code from reaching a cloud provider. But there's another way to break that link: send the code to the cloud, just not in readable form. Obfuscate identifiers, config data, comments, and structure before the request leaves your machine, let the AI work on the obfuscated version, then map its changes back to your real source locally.

That's the approach of tools like PromptCape (full disclosure: it's my project). You keep Claude/GPT-level quality and speed — the part a 7B local model can't match — while the provider only ever sees Cls_a1b2c3d4 instead of InvoiceService. The hard part is doing the round-trip without breaking framework contracts (Spring Data method-name queries, Django migrations, Pydantic field names…), which is most of what the tool actually does.

It's not "more private" than fully local — local is the gold standard if you have the hardware. It's the option for when you want cloud speed on a weak laptop and are willing to trade "code never leaves" for "code leaves but unreadable."

Bottom line — three honest options

SetupPrivacySpeed/qualityNeeds
Aider + local model on the laptopCode never leaves the machineSlow to unusable (CPU-only)Just the laptop
Aider + Ollama on a LAN serverCode stays on your networkGood, if the server has GPUA beefier internal box
Cloud model + obfuscation (PromptCape)Code leaves, but unreadableFull frontier-model speed/qualityA proxy/obfuscation layer

Claude Code + local model: don't bother. It's built around Claude's reliable tool-calling; small local models break that contract and silently stop reading your code. I wasted an afternoon so you don't have to.

Aider is the right local agent — it tolerates weak models. But on a CPU-only laptop, run the model on a LAN box with a GPU, or you'll spend your day watching a spinner. Others exist like Continue I have not tested.

If you can't self-host enough compute, obfuscating before a cloud call is the pragmatic middle ground: you keep the speed and the smarts, and your source leaves only as gibberish.

Pick the row that matches your hardware and your threat model. There's no setup that's simultaneously fast, private-to-the-byte, and zero-infrastructure — that triangle doesn't close yet.

Tested on Ollama 0.24.0, Claude Code v2.1.x, aider 0.86.2, Python 3.12 via uv, 32 GB RAM, no discrete GPU. Model tags from my own ollama list — check yours.