A HashSet “lost” my object after I changed one field. Walked through it in jshell

I wanted to see this one with my own eyes, so I typed it into jshell. Output is below with notes in between.

jshell> class Point {
   ...>     int x;
   ...>     Point(int x) { this.x = x; }
   ...>      public boolean equals(Object o) {
   ...>         return o instanceof Point p && p.x == x;
   ...>     }
   ...>     u/Override public int hashCode() { return Integer.hashCode(x); }
   ...> }

jshell> var set = new HashSet<Point>();
jshell> var p = new Point(1);
jshell> set.add(p);
$4 ==> true

Nothing odd yet. equals and hashCode both use x, which is what most IDEs generate.

jshell> p.x = 2;
jshell> set.size()
$6 ==> 1

jshell> set.contains(p)
$7 ==> false

jshell> set.remove(p)
$8 ==> false

This is the part that surprised me. The set says it has one element, and it says it doesn’t contain the exact object I’m holding a reference to. remove fails too.

jshell> for (Point q : set) System.out.println(q == p);
true

Iteration finds it. Iteration just walks every bucket, no hashing involved. contains and remove compute hashCode() first (now based on 2), go to that bucket, and find nothing. The object is still sitting in the bucket picked when its x was 1.

As far as I understand it, there’s no error because HashSet never learns the object changed. It only looks at the hash at insertion time and again at lookup time. If the two differ, you get a silent miss.

So the object is still in the set but unreachable by key. That’s the part that would be annoying to debug in a real codebase: size is right, iteration is right, lookups are wrong.

Things I’ve seen suggested for this:

  • keep fields used in hashCode final (records do this by default)
  • if the key has to change, remove it first, change it, add it again
  • use an immutable key type, like a String or a Long id, instead of the whole entity

Question for people who’ve worked on bigger projects: do you ever see this with JPA entities used as keys in sets, where the id is null before persist and set afterward? I’d guess it’s the same problem in a different outfit, but I haven’t tried it.

reddit.com
u/ClickOk5811 — 3 days ago

Agent built with LangGraph ran unattended overnight on a refactor. Redid two completed nodes that had already finished correctly hours earlier.

No crash, no error, no retry logged. It just circled back through nodes whose state said done and reprocessed them anyway, once with a slightly different naming convention than the first pass, overwriting the correct output with a worse one.

Assumed model drift at first. Doesn't fully explain it, the same model used interactively over a comparably long session doesn't usually fail this way nearly as often. The actual difference is that an interactive session has a human quietly doing a job nobody assigned them, noticing when a subtask's done and moving on, and the next message implicitly signals that shift without ever stating it outright.

A graph running unattended through a long task list has nothing playing that role by default. Even with state tracked per node, if the full working detail from completed nodes stays accessible in whatever gets fed into the next step's context, it's still competing for attention with the actual current step, not just sitting there as a settled fact. On a long run, that accumulates at the same rate as the task itself.

What's actually helped: explicitly collapsing a finished node's output into a short completion summary before it enters downstream context, instead of carrying the full reasoning trail forward, and treating a node's "done" state as something that actively shrinks what it contributes to context, not just a status flag nobody reads.

Wrote the longer version of this up here if useful: https://medium.com/@nagatomopedro05/unattended-ai-agents-have-a-context-problem-humans-never-had-60961eb2d4e7

u/ClickOk5811 — 8 days ago

"The scan passed" and "someone understood why it passed" are two different claims that get treated as one

SAST scan on a PR came back clean. Diff touched auth middleware. Scanner checked for known bad patterns, found none, green check, merged.

Nobody actually traced through what the middleware change did to the token validation order for an edge case that wasn't in the scanner's pattern set, because it wasn't a known vulnerability class, it was a logic gap specific to how this particular service combined two libraries. Found two weeks later during an unrelated audit.

The scan wasn't wrong. It checked what it was built to check and found nothing matching. The gap is that "scan passed" gets read as "this is secure," when it actually means "this doesn't match patterns we already know to look for." Those used to be close enough to the same claim that nobody bothered distinguishing them out loud. Novel logic gaps specific to how a service wires things together were always outside that coverage, just less common before more code started getting generated without someone necessarily tracing the full logic path by hand first.

Not arguing against scanning, obviously it's necessary. Just think "passed the scan" needs to stop functioning as a stopping point for anything touching an actual trust boundary, auth, payment, data access, and start being the floor instead of the finish line.

reddit.com
u/ClickOk5811 — 11 days ago

axe-core gave full marks. Tried it with VoiceOver anyway, out of habit more than suspicion.

Glad I did. Every image had alt text, every form field had a label, zero violations flagged on the automated scan. Then I actually tabbed through the checkout flow with VoiceOver running and hit a modal that opened silently, no focus moved into it, no announcement, nothing. The automated tool has no way to check where focus actually goes, only that the right attributes are sitting on the DOM.

Fixed the focus trap, added the announcement. The scanner would've passed either version. It was checking for the presence of the right pieces, not whether they did anything once someone started navigating with a real screen reader instead of a checklist.

Keeps reinforcing the same thing for me: automated a11y tools catch the stuff that's structurally checkable, they're not a substitute for someone actually using the thing with the tech it's meant to work with.

reddit.com
u/ClickOk5811 — 11 days ago

Query had used an index for two years. Then a migration in an unrelated table quietly stopped that.

EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 4821 AND status = 'pending';

Expected an index scan, same as it had run for two years. Got a seq scan instead, on a table with eleven million rows.

Turned out an unrelated migration had changed customer_id from int to bigint on the orders table, but a partial index used in a separate reporting service still referenced the old type implicitly through a cast that Postgres could no longer use for this predicate. Nobody touched the query. Nobody touched the index definition. The type change three services away was enough to make the planner quietly stop trusting it here.

Nothing failed loudly. Just got slower, gradually, until someone noticed the dashboard taking six seconds instead of forty milliseconds and started digging.

reddit.com
u/ClickOk5811 — 15 days ago

How are teams tracking cross-service assumptions that never get documented as a security boundary until one gets broken?

Had a change to a discount calculation pass code review clean, ship, and break an invoice service two days later. Not a vulnerability in the changed code itself, the invoice service had always assumed the incoming value arrived already tax-adjusted, and that assumption lived only in its own code, undocumented anywhere the reviewing service or its owners could see.

This isn't really about vulnerabilities, but the structural gap feels adjacent to trust boundary documentation: a review scoped to one service's diff can't see an assumption baked into a different service's code, the same way a review can't flag a missing auth check on a downstream consumer it was never shown.

For teams doing threat modeling or architecture review at any real scale: is there an established practice for surfacing undocumented cross-service assumptions like this before they become an incident, something like mandatory documentation of what a consumer assumes about an inbound value, contract tests that encode the assumption explicitly, or is this mostly still discovered reactively after something breaks?

reddit.com
u/ClickOk5811 — 16 days ago

"Why does the panel say 40% and the drive is actually full"

Me: Grafana panel's been green for months, shows 40% used on the array.
Also me, an hour later after actually running df -h on the box: it's at 94%.

Turned out the metric was scraped off one specific mount point via node_exporter, and a bind mount added during a Docker migration meant most of the actual writes were landing somewhere else entirely, a path the exporter never had a filesystem collector pointed at. Not a bug in the dashboard. It was rendering exactly the data it was given, one mount point, and I'd quietly changed what lived under that path without updating what was being scraped.

Green panels mean the thing you pointed the query at is fine. They don't mean the thing you actually care about is fine, and those are only the same claim until something upstream of the metric changes without the config catching up.

reddit.com
u/ClickOk5811 — 16 days ago

My B2 backup job reported success every night for a year. I never once restored from it.

Finally did a test restore into a scratch directory last weekend, mostly out of guilt.

b2 sync b2://my-bucket/appdata /tmp/restore-test
diff -r /srv/appdata /tmp/restore-test

The diff wasn't empty. One container's data directory had been moved during a compose refactor months ago, and the backup script's source path still pointed at the old location. So it had been syncing an empty folder up to B2 every night, exit code 0, job status green, a year of snapshots of nothing.

Nothing I was monitoring could have caught it, because I was checking that the sync ran, not that the bucket actually contained what I thought it did. A backup that's never been restored is really just a bill that looks like a backup.

Put a monthly restore-and-diff on the calendar now. Should've been there from day one honestly.

reddit.com
u/ClickOk5811 — 17 days ago

Telling a model "be brief" and "keep it under 50 words" produce different failure modes, not just different lengths

Noticed this testing a summarization prompt. "Be brief" gets you something short but the model decides what to cut, and it doesn't always cut the right thing. Sometimes it drops the actual conclusion and keeps the setup. "Under 50 words" gets you a hard constraint, but the model will sometimes mangle a sentence mid-thought to hit the count, or leave out something structurally important because it ran out of room and prioritized finishing a clause over finishing an idea.

Neither is strictly better. Depends what you're optimizing for. If the risk is rambling, a word limit fixes that reliably. If the risk is losing the point entirely, a word limit doesn't protect against that, it just guarantees short wrongness instead of long wrongness.

What's worked better for the cases where the point actually matters is naming the thing that can't be cut, separately from the length instruction. Something like "keep under 50 words, but the conclusion must always be stated explicitly, never implied." That splits the constraint into a hard limit and a hard requirement instead of hoping brevity and correctness cooperate on their own.

Still not sure this generalizes past summarization though. Curious if people doing this for other tasks, extraction, classification, anything with a defined right answer, have run into the same tension between length and completeness.

reddit.com
u/ClickOk5811 — 20 days ago

Myth vs. reality on why a "stable" shared module started producing inconsistent plans

Myth: The module is fine, Terraform's just being weird with this provider version.
Reality: The module had six conditional blocks added over a year, each one a fix for a specific edge case at the time. Nobody ever tested them together.

Myth: Small count and for_each tweaks are low-risk since they're isolated changes.
Reality: A conditional added to handle one team's use case can silently change behavior for a different team's use case if both paths share underlying resource logic, even when neither team's specific input changed.

Myth: If the module still applies clean, it's working correctly.
Reality: "Applies without error" and "produces the plan you'd expect" aren't the same claim. A module can apply fine while quietly resolving a conditional differently than intended, and nothing throws until someone notices a resource configured wrong weeks later.

Myth: More conditionals make a module more flexible and therefore better.
Reality: Each added conditional is competing logic inside the same evaluation, and past a certain point flexibility becomes unpredictability, because nobody's tracking which combinations of variables were ever actually validated together.

Myth: The fix is writing better conditionals.
Reality: The fix is usually splitting the module before it gets any more conditionals bolted on, or maintaining a fixed set of variable combinations that get terraform plan'd in CI before any change to the module ships, the same regression discipline any other shared code would get by default.

reddit.com
u/ClickOk5811 — 26 days ago

Inherited a reporting prompt from someone who left. Spent longer reverse-engineering why it says what it says than it would've taken to write a new one.

Notes from picking up a churn analysis prompt after the person who built it left the team:

Line 3 says "always express confidence as a range, never a single number." No comment explaining why. Took a support ticket search through old Slack to find out it was added after a stakeholder took a single confident-sounding percentage as guaranteed and made a decision on it that didn't pan out.

Line 9 excludes a specific customer segment from the trend calculation entirely, with no note on why that segment is different. Still don't fully know. Best guess from context is a data quality issue specific to that segment that got fixed upstream months ago, meaning the exclusion might not even be necessary anymore, but nobody's confident enough to remove it and find out.

Line 14 is an instruction that contradicts something implied in line 6, and the model apparently resolves that contradiction by favoring line 14, which nobody seems to have decided on purpose, it's just what happened to happen.

None of this was written down anywhere except inside the prompt itself, as instructions with no rationale attached. Every constraint made sense to the person who added it, in response to something specific that happened, and none of that context survived them leaving. What's left behaves correctly for reasons nobody currently on the team can fully reconstruct, which makes every future edit a small gamble on breaking something whose purpose is already lost.

reddit.com
u/ClickOk5811 — 27 days ago

When do you reach for ? vs. an explicit match on a Result, beyond just "less code"?

Getting AI-generated suggestions that default to ? almost everywhere, which compiles fine and is definitely more concise, but I'm not sure it's always the right call versus when I actually want to handle different error variants differently before propagating.

// Option A
fn process(input: &str) -> Result<Data, MyError> {
    let parsed = parse(input)?;
    Ok(transform(parsed))
}

// Option B
fn process(input: &str) -> Result<Data, MyError> {
    match parse(input) {
        Ok(parsed) => Ok(transform(parsed)),
        Err(MyError::Malformed(s)) => Err(MyError::Malformed(s)),
        Err(e) => {
            log::warn!("unexpected error: {:?}", e);
            Err(e)
        }
    }
}

Is there an actual rule of thumb for when B is worth the extra verbosity, only when you need a side effect like logging on a specific variant, or is there more to it than that? Also curious if idiomatic Rust generally leans toward keeping error handling this granular at every call site versus centralizing it further up the call stack.

reddit.com
u/ClickOk5811 — 28 days ago

Stop wrapping os.remove in try/except just to handle files that might not exist

Used to write this everywhere I needed to clean up a temp file that might or might not exist yet:

import os

try:
    os.remove(path)
except FileNotFoundError:
    pass

Path.unlink does this in one line with missing_ok:

from pathlib import Path

Path(path).unlink(missing_ok=True)

No exception ever gets raised if the file isn't there, so there's nothing to catch or pass on. Same behavior, one line instead of four, and it reads as "delete this if it exists" instead of "try to delete this and hope for the best."

Small thing, but it comes up constantly in any script that's cleaning up intermediate files, cache entries, or output from a previous run.

reddit.com
u/ClickOk5811 — 29 days ago

How are teams catching silent regressions in AI-assisted security triage prompts after incremental edits, versus treating each bad output as an isolated fix?

Had a prompt used to triage and categorize security findings by severity start missing things it used to catch reliably. Diffed it against an older version and found five small additions, each added to fix one specific bad output at the time, a rule about flagging hardcoded credentials, an example emphasizing high-severity findings, a line about keeping output concise for the ticketing system.

None of the five were individually wrong, but the interaction wasn't visible in any single diff. The credential-flagging rule, added after one missed secret, ended up quietly crowding out lower-severity findings on larger diffs, because both were competing for the same limited output space, and that regression got triaged as a separate, unrelated issue weeks later instead of being traced back to the earlier patch.

For teams running this kind of triage prompt at any real volume: is there an established practice for regression testing prompt changes against a fixed set of known findings before shipping an edit, the way you'd regression test a scanning rule change, or is this mostly still caught reactively after the fact the way we found it?

reddit.com
u/ClickOk5811 — 29 days ago
▲ 49 r/SCCM

Found a task sequence step added three years ago that exists purely to work around a bug nobody ever actually fixed

Task sequence step, added 2023: "Run PowerShell script to manually clear WMI repository corruption flag before proceeding to app install." Ran on every single deployment since, no exceptions, because leaving it out caused the install to fail on a meaningful chunk of machines.

Finally got investigated properly last month when someone new to the team asked why this wasn't just handled by ensuring WMI was healthy earlier in the build process instead of patching around corruption after the fact. Turned out a specific image customization step upstream was leaving WMI in a marginal state on certain hardware, never actually fixed, just permanently worked around at the task sequence level for three years. Whoever added that step solved their immediate deployment failure completely. Nobody ever went back to fix the actual image step causing the corruption, so the workaround just became permanent process, treated as a necessary part of every build instead of a flagged, temporary patch for a problem that should've been fixed once at the source.

Workarounds like this are efficient exactly because they solve the immediate failure completely, which is also what makes them so easy to leave in place indefinitely, there's never an obvious moment of pain that forces anyone to go back and ask why the step is actually there in the first place.

reddit.com
u/ClickOk5811 — 30 days ago

"Fixed the confusing paragraph" and "fixed why readers were confused" are different tickets, and doc backlogs rarely distinguish them

Reader feedback: "this section doesn't explain what happens if the field is left blank." Fixed by adding a sentence covering that case. Ticket closed, feedback addressed, technically true.

Three releases later, same complaint on a different section, same underlying gap, edge case behavior left unstated. Turned out the actual issue wasn't that one paragraph, it was that the whole doc set had never established a convention for documenting edge cases at all, so every section that happened to have one was equally likely to be missing it, and each missing instance read as an unrelated, isolated complaint until someone noticed they were all the same shape.

Patching the specific paragraph a reader flagged is a real fix for that specific complaint. It's not the same as fixing whatever let the gap exist in the first place, and the difference is easy to miss because both look identical in a closed ticket, addressed feedback, complaint gone. The tell is asking whether the same category of complaint would show up again in a section nobody's looked at yet, not whether this specific reported instance stopped generating complaints.

reddit.com
u/ClickOk5811 — 1 month ago
▲ 3 r/Habits

I keep treating the symptom of a bad habit instead of whatever's actually causing it, and I only just noticed the pattern

Every time I've tried to fix a habit, snacking late at night, procrastinating on a specific kind of task, I go straight for stopping the visible behavior. Doesn't work for long, because the behavior usually isn't the actual problem, it's just where an underlying condition happens to surface. Late-night snacking stops for a week through sheer willpower, then shows up again in a slightly different form, extra coffee, doom-scrolling, something else filling the same gap.

Realized I've never actually asked what condition keeps producing the urge in the first place, I just keep patching wherever it shows up next and treating each recurrence like a fresh, unrelated slip instead of the same root thing finding a new outlet. Stopping one specific behavior is a real, valid fix for that one instance. It's just not the same as addressing whatever keeps generating instances in the first place.

Starting to ask a different question when a habit resurfaces after I thought I'd handled it: if the specific trigger changed slightly, would this actually still hold, or would it just relocate to wherever the underlying thing finds room next.

reddit.com
u/ClickOk5811 — 1 month ago

Right-clicking "Suppress" in the Error List on a nullable warning does the same thing as an AI-generated #pragma disable, and neither one fixes anything

Had an AI tool "fix" a CS8602 warning by wrapping the line in #pragma warning disable instead of an actual null check. Warning disappeared from the Error List, build was clean, and nothing about the actual possibility of a null reference got addressed, just muted.

Realized afterward this is functionally identical to right-clicking the warning in the Error List and hitting Suppress, which VS happily lets you do in a couple clicks and which generates the exact same pragma pair. The IDE doesn't distinguish between "I've reviewed this and confirmed the null case is unreachable" and "I want this red squiggle gone," it treats both clicks the same way.

Started actually using the Error List's "Show Suppressed Messages" option regularly just to audit how many suppressions have accumulated over time without a documented reason attached, especially in code that had AI involvement at some point. Surprisingly easy to lose track of how many nullable warnings got silenced this way versus actually resolved, since a suppressed warning and a resolved one look identical in a clean build, they just aren't the same thing at all.

reddit.com
u/ClickOk5811 — 1 month ago

Never let AI-generated code swallow exceptions silently, always log or re-raise

Generated code reaches for broad exception handling constantly because it "works" in the sense that nothing crashes. The problem is it also hides the actual failure, so you find out something's broken from a symptom three steps downstream instead of from the actual error.

python

# What generated code often does
try:
    result = risky_operation()
except Exception:
    pass

# What actually helps you debug later
import logging

try:
    result = risky_operation()
except Exception as e:
    logging.exception("risky_operation failed")
    raise

The tip: any bare except Exception: pass should be treated as a red flag, not a working solution, especially in generated code where it looks intentional but usually just means the model produced something that avoids crashing without actually deciding what should happen on failure. At minimum log the exception with logging.exception() so the traceback isn't lost, and re-raise unless you have a specific, deliberate reason to continue silently.

reddit.com
u/ClickOk5811 — 1 month ago

A senior network engineer once told me "a good theory and a confirmed cause look identical until you check." Still true with AI-generated diagnostics.

Had an AI tool generate a fix for a connectivity issue, "asymmetric routing causing the resets, traffic going out the primary link and returning via backup," complete with a proposed config change to force symmetric routing. Specific, technically coherent, matched the symptom. Would've been easy to push it through the pipeline and call it fixed.

Checked routing tables on both ends before applying anything. Traffic was symmetric the whole time. Real cause was an MTU mismatch on one hop, completely different mechanism, similar-looking symptom on the surface, and the proposed fix would've done nothing for the actual problem while adding an unnecessary routing change to production.

The theory wasn't wrong because the reasoning was bad. Asymmetric routing is a real, common cause of exactly that reset pattern, so the explanation borrowed credibility from being a plausible category of problem without anyone confirming it was the actual instance happening here. That's a dangerous failure mode specifically in an automation context, because the gap between "plausible diagnosis" and "confirmed diagnosis" is exactly the gap that a human would normally catch by eyeballing the change before applying it, and automation pipelines are built to remove that eyeballing step for speed.

Now treating any AI-generated network change the same way: state what the diagnosis predicts should be true elsewhere in the system (in this case, actual routing asymmetry), verify that specific thing programmatically before the change reaches an apply stage, not just before a human signs off on it.

reddit.com
u/ClickOk5811 — 1 month ago