What is your BATNA?

When you are negotiating, you always want to be thinking about your Best Alternative to a Negotiated Agreement (BATNA). This applies in any negotiation, whether business, personal or even with yourself. When you have a better BATNA, you have more negotiating leverage and are more likely to get what you want out of it.

This is why when I was a contractor, I always had more than one client. Even if I was working with a good client who paid well, on time and was fun to work, I had more freedom if I had another client. Things might go south at the first client and I wouldn’t be out on the street. It’s also why I would always start looking for a contract 6-8 weeks before my current contract was finishing.

It’s why you should always get competing job offers. If you have a job offer and your best alternative is to keep job hunting, that’s not appealing. If, instead, you are choosing between two job offers, you are in a much better position. (No duh!)

It is also why it’s always easier to get a job if you have a job. The BATNA of declining a job offer when you have employment is, well, you remain your current position. Your current job may not be all that awesome (which is why you are looking) but for most folks being employed is a better alternative than being unemployed.

How can you use the concept of BATNA to improve your life?

First, be aware of the concept. Start to look at decisions in your life and think about the BATNA. Even small decisions, like ‘should I get coffee or nothing’? Or ‘what happens if I ask my wife to take out the garbage’? Or ‘should I ask for a raise’? In all of these cases, you can expect some kind of negotiation, and you can think about what the alternative is if that negotiation fails.

Second, take actions to improve your alternatives. If you are unemployed and want more leverage in the job hunt, start consulting. If your wife won’t take out the trash, can you improve your BATNA by making it easier to take out the trash yourself (maybe move the trash can into the garage)? Or building some kind of trash chute?

The concept of a BATNA is key to getting the most out of any negotiation. If you have good alternatives, you have more leverage to leave the negotiation, and if you don’t, you will need the negotiation to complete successfully.

More about BATNAs and salary negotiation here.

When should you leave a job?

Saw a great post by Kathy Keating, on a slack of which I am a member. This is how she suggests thinking about whether to stay or go at a given position:

1) Are there ethical issues? Leave.

2) Are there leadership issues?


2a) Can I have transparent/open dialog and is there hope that the leadership issue can be addressed? Timebox length of stay.

2b) No dialog. No hope of addressing issues. Leave.


3) Are there systemic company process issues?


3a) Company knows and is addressing the issue. Timebox length of stay.

3b) Company is fine being a mess. Leave.


4) Do I have career growth?


4a) No. Leave.

4b) Yes. Stay if no other blockers.

This is a lightweight framework to consider when you have a bad day or week at work. It’s important to acknowledge that:

  1. I (and most other software developers) are very privileged when it comes to job expectations. (My SO worked in healthcare and the stories, oh the stories.)
  2. The grass is not always greener and if you stay you might have credibility to effect change.

I think there are other factors that might play in here like type of work, location of work, and compensation, but in general I love this flow chart of how to decide whether or not to stay in a job. If there are other items that are important to you, add them into the decision tree, but having something like this will make it easier to make a hard decision like leaving a company.

PS No post about deciding to leave a job would be complete without referencing Shields Down and Bored People Quit.

Noticing blocked charges in stripe

We recently had our first blocked charge at The Food Corridor. We found out because Stripe kindly sent us a notice. It was blocked because Stripe’s algorithms determined that it was possibly fraudulent.

This is a pretty rare event (hopefully) but we still want to be prepared to deal with it. In the evolution of admin features, this is definitely at level 2, because it involves customers and money.

Luckily stripe_event has our backs and we can easily catch a webhook event and send an email if this happens. We just need to figure out what the event looks like.

I searched the stripe API but found nothing about blocked charges. I sent an email into Stripe’s helpful support system, and shortly thereafter someone had tracked down the event so that we could set up that webhook. Here’s the event (anonymized):

  "object": {
    "id": "ch_xxx",
    "object": "charge",
    "amount": 100,
    "amount_refunded": 0,
    "application": null,
    "application_fee": null,
    "balance_transaction": null,
    "captured": false,
    "created": 111,
    "currency": "usd",
    "customer": "cus_xxx",
    "description": "desc",
    "destination": "acct_xxx",
    "dispute": null,
    "failure_code": "card_declined",
    "failure_message": "Your card was declined.",
    "fraud_details": {
      "stripe_report": "fraudulent"
    "invoice": null,
    "livemode": true,
    "on_behalf_of": "acct_xxx",
    "order": null,
    "outcome": {
      "network_status": "not_sent_to_network",
      "reason": "highest_risk_level",
      "risk_level": "highest",
      "rule": {
        "id": "block_if_high_risk__enabled",
        "action": "block",
        "predicate": ":risk_level: = 'highest'"
      "seller_message": "Stripe blocked this charge as too risky.",
      "type": "blocked"
    "paid": false,
    "receipt_email": null,
    "receipt_number": null,
    "refunded": false,
    "refunds": {
      "object": "list",
      "data": [
      "has_more": false,
      "total_count": 0,
      "url": "/v1/charges/ch_xxx/refunds"
    "review": null,
    "shipping": null,
    "source": {
    "source_transfer": null,
    "statement_descriptor": "descriptor",
    "status": "failed",
    "transfer_group": "group_ch_xxx"
  "previous_attributes": null

The key is to look at the outcome type value, and handle blocked failed charges differently than normal failed charges (perhaps by contacting the customer).

Always break rails migrations into smallest chunks possible, and other lessons learned

So this was a bit of a sticky wicket that I recently extracted myself from and I wanted to make notes so I didn’t make the same mistake again. I was adding a new table that related two existing tables and added the following code

class CreateTfcListingPeople < ActiveRecord::Migration
  def change
    create_table :tfc_listing_people do |t|
      t.integer :listing_id, index: true
      t.string :person_id, limit: 22, index: true

      t.timestamps null: false

    add_foreign_key :tfc_listing_people, :people
    add_foreign_key :tfc_listing_people, :listings


However, I didn’t notice that the datatype of the person.id column (which is a varchar) was `id` varchar(22) COLLATE utf8_unicode_ci NOT NULL

This led to the following error popping up in one of the non production environments:

2018-02-27T17:10:05.277434+00:00 app[web.1]: App 132 stdout: ActionView::Template::Error (Mysql2::Error: Illegal mix of collations (utf8_unicode_ci,IMPLICIT) and (utf8_general_ci,IMPLICIT) for operation '=': SELECT COUNT(*) FROM `people` INNER JOIN `tfc_listing_people` ON `people`.`id` = `tfc_listing_people`.`person_id` WHERE `tfc_listing_people`.`listing_id` = 42):

I was able to fix this with the following alter statement (from this SO post): ALTER TABLE `tfc_listing_people` CHANGE `person_id` `person_id` VARCHAR( 22 ) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL.

But in other environments, there was no runtime error. There was, however, a partially failed migration, that had been masked by some other test failures and some process failures, since there was a team handoff that masked it. The create table statement had succeeded, but the add_foreign_key :tfc_listing_people, :people migration had failed.

I ran this migration statement a few times (pointer on how to do that): ActiveRecord::Migration.add_foreign_key :tfc_listing_people, :people and, via this SO answer, I was able to find the latest foreign key error message:

2018-03-06 13:23:29 0x2b1565330700 Error in foreign key constraint of table sharetribe_production/#sql-2c93_4a44d:
 FOREIGN KEY (person_id)  REFERENCES people (id): Cannot find an index in the referenced table where the
referenced columns appear as the first columns, or column types in the table and the referenced table do not match for constraint. Note that the internal storage type of ENUM and SET changed in tables created with >= InnoDB-4.1.12, and such columns in old tables cannot be referenced by such columns in new tables.
Please refer to http://dev.mysql.com/doc/refman/5.7/en/innodb-foreign-key-constraints.html for correct foreign key definition.

So, again, just running the alter statement to change the collation of the tfc_listing_people table worked fine. However, while I could handcraft the fix on both staging and production and did so, I needed a way to have this change captured in a migration or two. I split apart the first migration into two migrations. The first created the tfc_listing_people table, and the second looked like this:

class ModifyTfcListingPeople < ActiveRecord::Migration
  def up
    execute <<-SQL
      ALTER TABLE  `tfc_listing_people` CHANGE  `person_id`  `person_id` VARCHAR( 22 ) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL

    add_foreign_key :tfc_listing_people, :people
    add_foreign_key :tfc_listing_people, :listings
  def down
    drop_foreign_key :tfc_listing_people, :people
    drop_foreign_key :tfc_listing_people, :listings

Because I’d hand crafted the fixes on staging and production, I manually inserted a value for this migration into the schema_migrations table to indicate that the migration had been run in those environments. If I hadn’t had two related but different migration actions, I might not have had to go through these manual gyrations.

My lessons from this episode:

  • pay close attention to any errors and failed tests, no matter how innocuous. This is a variation of the “broken window theory”
  • break migrations into small pieces, which are easier to debug and to migrate back and forth
  • knowing SQL and having an understanding of how database migrations work (they are cool, but they aren’t magic, and sometimes they leak) was crucial to debugging this issue

“I want it all and I want it now”

Do you want it all? Should you?

Jason Cole has some wise words on the various hires you can make to make your life easier as a technical founder.

There’s a myth in our industry that you can write code and manage people, and that every engineer should do both if they want to advance their career. While I know people who are good at both, they’re generally the rare kinds of people who can hold two opposing concepts in their minds at the same time. And they never get to straddle those two areas for long; they either become a senior people leader or a senior technologist.

This is the old CTO vs VP of Engineering split.

So, as you grow in a company as a technical founder (or even an early engineer) you need to make a decision. And when you are hiring, instead of searching for someone who can wear all the hats that you used to, find someone who can take two or three of your hats and put them on. It’s the beginning of the transition from generalists to specialists.

How would you build a simple CRUD app in 2018?

Interesting discussion on HN about how you’d build a simple CRUD app in 2018. (CRUD stands for Create, Read, Update and Delete of records in a system.) As you’d expect, no shortage of opinions, with a lot of specifics. Also lots of recommendations to use what you know, which is always a good idea. CRUD apps, where you are simply gathering data via a web interface, run the gamut of complexity and usability, but it’s hard to believe how powerful having a centralized repository of data can be.

And the nice thing is that once you get the data into the backed of  a CRUD app (which is likely a SQL compatible database, but could be any other kind of datastore) you can either combine that data with other systems, expose the data via an API, or both.

Here was my answer to the question:

I’d use Rails today. But I can see the value in Django or Express or ASP.Net or Spring Boot.


Criteria I’d think about:


What is going to be easy for my organization to support (both operationally and with future code changes).


What do I know/want to learn? If the CRUD app is complicated, then I want a tech I know well. If the CRUD app is simple, then I may want to experiment with a different technology (again, within the organizational support guardrails).


When is a senior engineer not a senior engineer?

The technology industry has been overloading the term ‘senior engineer’. A senior engineer is not a senior engineer is not a senior engineer.

What “senior” really means depends on what your organization needs and how it operates. Much of my experience has been in small organizations, so this list may be tilted more toward jack of all trades, but I’ve seen some of these patterns at larger companies as well. Here are a number of attributes that all seem to get wrapped up in the word “senior”.

Domain knowledge: sometimes getting up to speed on a domain can take too long. If you are operating in a highly regulated or intricate problem domain (real estate, finance, health care, government contracts), senior may mean “this is not their first rodeo”. In other words, someone who can jump in and understand complicated entity relationships and acronyms quickly.

Deep technical skill in the problem space: someone who has been there before. ‘There’ is a technical area where screwing up is going to be very bad for the company, for example, scaling an application, running a database or securing an environment.

Deep technical skill in the exact technology: someone who has used your exact technology stack before. The idioms, the libraries, the warts, all of these vary enough and if you want someone senior in technology, a close substitute won’t do. If your app is in Django, you need a senior Django person, you don’t need or want someone with Rails or Laravel experience. Same for someone with PostgreSQL experience (as opposed to MySQL). I see lots of job applications where this was a belief, but fewer organizations where this is a reality. This is often just a simple way to filter applicants.

Broad technical skill in a related area: someone who has used something similar and can come up to speed quickly by making analogies between similar situations. Maybe you can’t find that exact match. In that case, you can broaden your search. At a hight level, MySQL and PostgreSQL have a lot of similar characteristics, and mapping knowledge of PostgreSQL into MySQL (or vice versa) can be effective. This senior developer works best when paired with a someone with “deep technical skill in the exact technology” person because then they can pick up idioms and libraries.

Utility player: this type of senior developer can fill gaps in your team. They notice what isn’t being done, whether that is setting up a build system, documentation, project management, user testing, design or something else, and either do it or advocate for it. Or both. Probably more important in smaller organizations.

Leadership: this is the ability of a senior developer to lead a team toward a business goal. This includes understanding why the goal is important, caring about the goal, communicating the goal to the team, and rallying the team when the goal seems unattainable.

Mentoring: this is the ability to develop other talent in your organization. Whether or not there is a formal mentorship program, skills transfer and teachable moments happen every day (and are not always performed by the more experienced party!). Doing this requires empathy. If your organization has a lot of less experienced developers, a more formal program may be useful.

Humility: senior developers are senior because they’ve made mistakes. This is the ability to acknowledge mistakes, learn from them, and try to figure out how to make only new mistakes.

Continuous learner: this type of senior developer is looking at new technologies and developments and seeing how they map into the current problem space. Often they are just “playing” with technologies on their own time. If they’re really good, they’ll acknowledge their “shiny object” syndrome and advocate for ways to explore new technology that don’t impact long term maintainability (spikes, hackathons).

Cross department communication: the ability to build a mental model of who knows/owns what in your organization. When hiring a new developer, they won’t have this map, but they may have built one for previous organizations. Someone with this knowledge knows who to ask to get something done, or who needs to be notified when changes are coming. This can prevent the significant pain of building the right solution to the wrong problem. Leveraging someone with this skillset cuts down on formal process, but eventually these communication channels may need to be formalized as the organization grows. Probably more important in smaller organizations.

Project management: depending on the size of your team or organization, a senior developer may need to be the adhoc project manager. They probably won’t enjoy this much, but someone has to do it. They’ve seen what happens when someone doesn’t (see “Humility” above).

Development support/operations/devops: this is the glue around your application that helps your application function. These tasks can range from developer focused tasks like a coding style guide or maintaining the developer docker image to operations tasks like setting up the monitoring system to devops tasks like debugging the failing jenkins job.

Deep knowledge of current application: this obviously isn’t a senior position you can hire for, but is one that a developer can grow into. This is the senior person that knows all the answers about the growth and path of the application code. If they’re really good, they document and share the knowledge.

When you are posting a job description for a “senior” engineer, think about what dimensions listed above matter. You can’t find someone who is good at everything, so what do you really need? What does your organization need?

“Deeply problematic but practical advice”

This post from Charity, who I believe I first started following when she presented at a gluecon about parse, is excellent and speaks to some of the strategies she’s used to succeed in technology.

If you feel like table flipping out of tech, just remember the rest of the world is at LEAST as sexist as tech is, but without the money and power and ridiculous life-coddling. Where exactly do you think you’re going to go?

Several hundred words of zero bs. Worth a read. And if you’re not a member of a marginalized group, it’s a great read to give you a taste of what it must be like. At least that’s what I took from it.

Update 3/5/18: My wife pointed out that she was offended by this post (a bit tone deaf, to paraphrase). I just want to be clear that I’m not a member of any marginalized group and just wanted to call attention to what I thought was a post documenting strategies, which I inferred were in response to some problems that have been publicized recently.

Go Code Colorado is coming soon

This is yet another hackfest, but one with a bit more pedigree. Go Code Colorado has a goal of improving access to public data. The stated mission: “Build a product that uses public data to deliver insights for a business decision-maker.” This will be the fifth year of this event. If you are interested in participating, the key dates are April 13-15 and April 27-29. They are going to run the hackfest on April 13-15 in five Colorado cities, so even if you aren’t on the front range, you have the chance to participate.

After seeing some folks talk about this on a slack (including one who said she found a co-founder through the event), I just volunteered to mentor, which can take as few as a few hours on a weekend (though I’m sure they’d be happy to have you for more time).

Past winners have included Magpie Supply and Regulation Explorer, and they produce a report each year.

If you are interested in building something for the public good, go check out Go Code Colorado.

CircleCI shutting down version 1.0

I’ve been a happy user of CircleCI at multiple companies. Right now we pay them at The Food Corridor and they handle almost all our deployments. (I still deploy to production manually.)

We just got a note that they are shutting down their 1.0 offering and will not support it as of Aug 2018. The 2.0 offering was announced in 2016 and generally available in 2017. So, there will be about one year of overlap. Not too long.

I understand that desire to move forward.Trust me, I do.

I don’t know how much engineering effort it takes to support the two versions, but my guess is that they’ll see some significant customer loss from this. Why? CI is something that you just want to work. You don’t want to think about it. Which is why a SaaS solution makes so much sense. I am happy to just keep paying them month after month for their excellent product.

But, if I have to take some cycles to move from CircleCI 1.0 to CircleCI 2.0, why wouldn’t I take some time and evaluate other solutions too? I assume they’ve run the numbers and the amount of money it takes to support 1.0 must be more than the amount they will lose via churn.

AWS does a good job of this–they never deprecate anything (you can still set up SimpleDB if you want). They just hide it, make other offerings better, and make older offerings more expensive.

In fact, if I were CircleCI, I might offer a ‘legacy’ CircleCI 1.0 plan, where people with significant investments in the older infrastructure can pay more for access to that old codebase. Depending on the amount of support required, that might be some significant free money.

Relatedly, Amy Hoy has a great post on how to get your customers to pay you more money.

© Moore Consulting, 2003-2017 +