Build a Javascript Slack App

A Summary of the Official Building a Bolt JS App

  1. Create a new slack app in the apps portal.
  2. Go to Basic Information page in the apps portal and install the app in your Slack instance.
  3. To edit and host the code for your slack app: Create a Glitch app from Slack’s template.
  4. Copy the signing secret and the Bot token from Basic Information and OAuth pages in the apps portal into your glitch app’s .env file.
  5. Go to Event Subscriptions in the apps portal and add app_home_opened.
    1. request_url for glitch apps is
    2. Add bot event app_home_opened. Save changes.
  6. Go to your app’s “home” page in the Slack app. You should see the example text and button from the code in Glitch.
  7. You can now use shortcuts, listen for events, post messages, and more with the bolt js framework. To try one, just replace the example “app.home.opened” function.
  8. Each of the functions above has listener functions for interacting with Slack. 

Add a Shortcut in a Bolt JS Slack App

  1. Go to your app’s Interactivity page in the apps portal.
    1. request_url for glitch apps is
    2. Name is whatever you want showing up in the Slack app.
    3. callback_id is your function name that you will use in your code.
  2. In glitch, add the code from
  3. That’s it. To add more functionality to your shortcut, check out
slack shortcut example
slack shortcut example

Kubernetes FAQ

Recently I’ve been working a lot with kubernetes at my job. I’ve written up a bunch of internal documentation based on my work, and because it’s not company-specific I decided I’m going to share it here for anyone trying to find answers on the internet.

Table of Contents:

  1. what is k8s?
  2. view nodes in a nodegroup
  1. view pods on a node
  2. restart deployment
  3. insufficient cpu, insufficient memory errors
  4. deployment is not ready
  5. CrashLoopBackoff
  6. pod didn’t trigger scale-up
  7. how do I avoid accidentally running a kubectl command on the wrong cluster?

What is Kubernetes? How do I use it?

Check out kubernetes docs here:

View Nodes in a Nodegroup

Confirm the nodegroup you’re checking actually exists (e.g. you’re looking for pods running under engtools-infra/my-app) by listing all the nodegroups in a certain namespace:

k get nodegroup -n engtools-infra

Get the nodes under that nodegroup:

k get nodes -o wide | grep my-app

View Pods on a Node

View pods under a node with the following command (replace NODE_NAME_GOES_HERE with the node’s name, e.g. ip-10-10-12-123.ec2.internal).

k get pod --field-selector=spec.nodeName=NODE_NAME_GOES_HERE -owide --all-namespaces | grep -v -E "( kube-proxy-| kube2iam-|  local-volume-provisioner-| node-monitoring-| localusers-)"

Restart Deployment

Make sure you’re in the right cluster, get the deployment name, then use the rollout command.

k config current-context
k get deployment -n [NAMESPACE]
k rollout restart deployment [DEPLOYMENT_NAME] -n [NAMESPACE]

insufficient cpu, insufficient memory errors

If you’re seeing errors about insufficient resources, or node(s) had taint … that the pod didn’t tolerate warnings, you probably need to define instanceType and resources attributes in your service k8s objects / charts. For example, something like the following:

  instanceType: c5.2xlarge
      cpu: "3"
      memory: "4G"
      cpu: "4"
      memory: "6G"

Deployment is not ready

A log you might see when deploying with Bazel is Deployment is not ready: <service>. 0 out of 2 expected pods are ready. This usually means k8s pods are in a CLB or some other non-ready status.

Use kubectl commands to check on the pods’ status and logs. Usual status check command:

kubectl -n NAMESPACE get pods


If you see CrashLoopBackOff status on one of your pods, that means it’s crashing (I know, thanks captain obvious).

Check the pod logs: k logs CRASHING_POD -n NAMESPACE.

Find the deployment.yaml file describing the launch instructions for this pod. Find the command attribute in that file, comment out the value underneath it, and add the following:

          - tail
          - "-f"
          - /dev/null

Then re-deploy the pod. This will allow you to SSH into the pod, run the failing command manually, and generally get more info about the problem.

You might also need to comment out the liveness/readiness container specs in deployment.yaml, as those will often cause the pod to crash as well if there’s an issue with the primary container.

More info about troubleshooting crashloopbackoff pods can be found in this article.

pod didn’t trigger scale-up

Normal   NotTriggerScaleUp    19m                  cluster-autoscaler   pod didn’t trigger scale-up: 3 node(s) had taint {node: database-rev-eng-8c38-pool1}, that the pod didn’t tolerate…

Potentially a node group or a scaling group issue. For example, you might have a single AutoScalingGroup (asg) under your nodegroup-controller which has reached its max size. In this case, adding autoscaling: true option to your charts may be required in order to have more ASGs created.

If adding nodegroup options, you may need to delete the existing nodegroups (krm ng NODEGROUP_NAME -n NAMESPACE) for the option to take effect. After creating the new nodegroup, the cluster autoscaler may need to pass by, which can take a few minutes. So if you’re still seeing the scale-up messages and your pod in a Pending status after such a change you may need to give it a little time.

How do I avoid accidentally running a kubectl command on the wrong cluster?

Use Explicit Context in Commands

kubectl –context get pods -n mynamespace  


kube-ps1 will show you what cluster and namespace you’ve set in your prompt, which helps avoid this issue:

After installing it with homebrew, add this to your .rc file and restart your terminal to enable it:

source /usr/local/opt/kube-ps1/share/

It works great in conjunction with using kubectx and kubens to change your context/cluster/namespace.

It’s very easy to change contexts in a different terminal and have a stale prompt showing the old context. Press enter to refresh the prompt.


If you use powerlevel10k, it can dynamically update your prompt with the current context when you’re typing a kubectl command –


‘Clear Formatting’ Shortcut Fix

Recently my ‘clear formatting’ shortcut for most google apps (Command (or Cmd) ⌘ + \) stopped working on my Macbook.

This was very frustrating as I use it all the time.

Apparently the 1password took over this shortcut since it uses cmd+\ to fill logins, silently. I was able to change the shortcut in 1password preferences, at which point clear formatting started working again.

In the future, if you have a similar issue, there’s also an app called ShortcutDetective that can help you find out if a keyboard shortcut is being used for an app on your Mac.

View Pods in a Nodegroup

Confirm the nodegroup you’re checking actually exists (e.g. you’re looking for pods running under product-team/fubar) by listing all the nodegroups in a certain namespace:

get nodegroup -n product-team

Get the nodes under that nodegroup:

get nodes -o wide | grep fubar

Get the pods under those nodes (replace NODE_NAME_GOES_HERE with the name you got from the command above, e.g. ip-10-111-22-333.ec2.internal):

k get pod --field-selector=spec.nodeName=NODE_NAME_GOES_HERE -owide --all-namespaces

Parking Near Airports in NYC

Little trick I learned from living in NYC.

There’s free street parking in neighborhoods like Ozone Park near JFK on streets that don’t have street cleaning, for example on 120th St between 133rd Ave and Sutter Ave. You can park there, and then take public transit or uber to JFK. I’ve done this at least twice now.

This is fairly low risk if you’re not going away for more than a week. The only issue is if you leave the car for too long I imagine it might be reported as abandoned and get towed.

You can do a similar sort of thing near LGA, which I’ve done MANY times now, but there are no streets without street cleaning, so you either need to make sure your trip is 6 days or less, or cough up the $65 ticket for street cleaning.

The nice thing about LGA, is that street parking is close enough to the airport that you can just walk to the terminal, or take a kick scooter and then lock it up at one of the bike racks outside the terminals.

I love not needing to spend $60-80 for airport transportation and being able to drive myself 95% of the way there. I usually walk to LGA too instead of taking an Uber after finding a spot.

Bulk Delete Projects in Jira

Had to do this recently for a lot of test projects that were created in our Jira sandbox instance.

It’s a small python script using Jira API and the requests python library.

import requests
from requests.auth import HTTPBasicAuth

domain = ""
admin_user = ""
admin_token = "JIRA_ADMIN_TOKEN"
project_query = "PROJECTS_I_WANT_TO_DELETE"

def getJson(url, auth):
    r = requests.request(
        headers={"Accept": "application/json"},
    return r.json()

def deleteProject(id, auth):
    r = requests.request(
        domain + "/rest/api/3/project/" + id,
    return r.text

search_url = domain + "/rest/api/3/project/search?query=" + project_query
auth = HTTPBasicAuth(admin_user, admin_token)
json = getJson(search_url, auth)

projectIds = []

# append results across all pages, while there's still a nextPage, to projectIds array
while "nextPage" in json:
    nextUrl = json["nextPage"]
    for searchResult in json['values']:
        # optional safety check to make sure the right project is being added to the deletion array
        # if "PROJECT_NAME_I_INTEND_TO_DELETE" in searchResult["name"]:
    print("Number of project IDs found matching the search query: " + str(len(projectIds)))
    json = getJson(nextUrl, auth)

# append a single page, or the last page of results, to projectIds array
for searchResult in json['values']:
    # optional safety check to make sure the right project is being added to the deletion array
    # if "PROJECT_NAME_I_INTEND_TO_DELETE" in searchResult["name"]:
print("Number of project IDs found matching the search query: " + str(len(projectIds)))

# delete projects in projectIds array
for index, id in enumerate(projectIds):
    print("Deleting project " + id + ". Projects remaining: " + str(len(projectIds)-index))
    print(deleteProject(id, auth))

Sample SMTP interaction

this is what happens every time you send an email, between the receiving server and the transmitting client – it’s kinda cute

the contents of the email are after server says “354 Enter mail…”

Server: 220
Client: HELO
S: 250 Hello, pleased to meet you
S: 250… Sender ok
S: 250 … Recipient ok
S: 354 Enter mail, end with “.” on a line by itself
C: Do you like ketchup?
C: How about pickles?
C: .
S: 250 Message accepted for delivery
S: 221 closing connection

IE9’s toolbar size versus other browsers

It seems with every new version of a web browser, the toolbar gets smaller and smaller. This makes a lot of sense since, as a user, you want as much space as possible for the websites you’re visiting.

In the latest version of Internet Explorer 9, the toolbar panel takes the most minimalist approach ever taken with IE. It actually beats Google Chrome in terms of least amount of space used:

This is partially achieved by combining the search and the address bar as Chrome has done when it was first released, and by leaving only the most essential navigation buttons.

The size difference becomes even more drastic when compared to Firefox 3, which, even when you remove the default bookmarks toolbar, is at least twice the size of IE9’s toolbar.

You can definitely tell that Microsoft is making every effort in making sure users don’t have any need for alternative browsers, and has learned a few lessons from its competitors while implementing its own innovations.

AT&T’s Tethering Costs Infinity Per Megabyte

I was recently sent this article:

It basically talks about how text messaging fees have you paying $1,310 per megabyte of data. Which is true – texting fees were always ridiculous and somehow wireless carriers were able to capitalize on that.

Tethering is a little different. When you use tethering (a feature that was built into the iphone in 3.0 over a year ago: your phone acts as an internet connection relay/access point for your laptop. You’re using your data plan on your laptop instead of your phone.

Naturally a person would use more data on their laptop than on their phone as it’s more convenient to watch online videos, write emails, etc. on a laptop. In the past a carrier would charge extra for tethering since data plans were unlimited but more data usage is a higher load on a network that a carrier has to maintain and pay for. However, with data caps on all of AT&T’s plans, using more data than your plan covers already results in heavy fees ($10 for every 1GB overage in the 2GB plan).

So charging an extra $20 a month just to be able to tether is just AT&T saying “we own your asses.”

An excerpt from an interview with Mark Collins, senior VP of data and voice products at AT&T:

GigaOM: What about the $20 tethering fee? It looks like a convenience charge.

Collins: That capability is enabling something you can’t do today. You can use one device and get multiple connections so it’s more useful to you. You’re going to use more data so the price is based on the value that will be delivered.

Enabling something AT&T disabled last year. Using data which already increases in cost depending on how much you use.

Simple analogy: Imagine Apple introduced a feature in their older iPhones (original, 3g) that allowed them to record videos using the camera and email them to your friends. AT&T disabled that feature on the account that you will probably end up using more data with it since you will be emailing videos. However, they finally decided they are going to charge $20 per month for “enabling” it and because “you’re going to use more data.”